/* Audio File Library Copyright (C) 2000, Silicon Graphics, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA. */ /* modules.c */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "afinternal.h" #include "modules.h" #include "pcm.h" #include "util.h" #include "units.h" #include "compression.h" #include "byteorder.h" #include "print.h" #include "rebuffer.h" #ifdef DEBUG #define CHNK(X) X #define DEBG(X) X #else #define CHNK(X) #define DEBG(X) #endif #define NULLMODULEPARAM extern _PCMInfo _af_default_signed_integer_pcm_mappings[]; extern _PCMInfo _af_default_unsigned_integer_pcm_mappings[]; extern _PCMInfo _af_default_float_pcm_mapping; extern _PCMInfo _af_default_double_pcm_mapping; extern _CompressionUnit _af_compression[]; /* Define rebuffering modules. */ extern _AFmodule int2rebufferv2f, int2rebufferf2v; /* module utility routines */ /* _AFnewmodinst creates a module instance from a module. It returns a structure, not a pointer to a structure. */ _AFmoduleinst _AFnewmodinst (_AFmodule *mod) { _AFmoduleinst ret; ret.inc = ret.outc = NULL; ret.modspec = NULL; ret.u.pull.source = NULL; ret.mod = mod; ret.free_on_close = AF_FALSE; ret.valid = AF_FALSE; return(ret); } /* _AFfreemodspec: useful routine for mod.free function pointer */ void _AFfreemodspec (_AFmoduleinst *i) { if (i->modspec) free(i->modspec); i->modspec = NULL; } /* _AFpull: used a lot -- see comments in README.modules */ AFframecount _AFpull (_AFmoduleinst *i, AFframecount nframes2pull) { _AFmoduleinst *src = i->u.pull.source; i->inc->nframes = nframes2pull; CHNK(printf("%s pulling %" AF_FRAMECOUNT_PRINT_FMT " frames from %s\n", i->mod->name, i->inc->nframes, src->mod->name)); (*src->mod->run_pull)(src); CHNK(_af_print_chunk(i->inc)); CHNK(printf("%s received %" AF_FRAMECOUNT_PRINT_FMT " frames from %s\n", i->mod->name, i->inc->nframes, src->mod->name)); return i->inc->nframes; } /* _AFsimplemodrun */ void _AFsimplemodrun_pull (_AFmoduleinst *i) { _AFpull(i, i->outc->nframes); (*i->mod->run)(i->inc, i->outc, i->modspec); } /* _AFpush */ void _AFpush (_AFmoduleinst *i, AFframecount nframes2push) { _AFmoduleinst *snk = i->u.push.sink; i->outc->nframes = nframes2push; CHNK(printf("%s pushing %" AF_FRAMECOUNT_PRINT_FMT " frames into %s\n", i->mod->name, i->outc->nframes, snk->mod->name)); CHNK(_af_print_chunk(i->outc)); (*(snk->mod->run_push))(snk); } /* _AFpushat */ void _AFpushat (_AFmoduleinst *i, AFframecount startframe, bool stretchint, AFframecount nframes2push) { _AFmoduleinst *snk = i->u.push.sink; void *saved_buf = i->outc->buf; i->outc->buf = ((char *)i->outc->buf) + (_af_format_frame_size_uncompressed(&i->outc->f,stretchint) * startframe); i->outc->nframes = nframes2push; CHNK(printf("%s pushing %" AF_FRAMECOUNT_PRINT_FMT " frames into %s " "with OFFSET %" AF_FILEOFFSET_PRINT_FMT " frames\n", i->mod->name, i->outc->nframes, snk->mod->name, startframe)); CHNK(_af_print_chunk(i->outc)); (*(snk->mod->run_push))(snk); i->outc->buf = saved_buf; } /* _AFsimplemodrun */ void _AFsimplemodrun_push (_AFmoduleinst *i) { i->outc->nframes = i->inc->nframes; (*(i->mod->run))(i->inc, i->outc, i->modspec); _AFpush(i, i->outc->nframes); } /* These macros each declare a module. The module uses _AFsimplemodrun_pull and _AFsimplemodrun_push (see comments in README.modules). Thus we only have to define one routine that does the actual processing. The arguments to the macros are as follows: name - name of module desc - code for module's "describe" function (see README.modules) intype - type of elements of input buffer outtype - type of elements of output buffer action - action to take in inner loop -- indexes "ip" and "op" with "i" modspectype - (MODULEM) this will initialize a pointer "m" to this instance's modspec data, which is of type modspectype Don't use _MODULE directly. The code in "desc" is executed once, after the module is initialized. It can reference "i->modspec" and should modify "i->outc->f". A pointer "f" initialized to "&i->outc->f" is emitted prior to "desc"; use this to to keep the code cleaner. Note that the generated "run" routine shouldn't set outc->nframes since * outc->nframes is set to inc->nframes by _AFsimplemodrun_push * inc->nframes is set to outc->nframes by _AFsimplemodrun_pull The whole point of the simplified "run" routine is that you don't have to worry about push or pull. See README.modules for more info on how modules work. */ #define _MODULE( name, desc, \ intype, outtype, chans, preamble, action, postamble )\ static void name##run(_AFchunk *inc, _AFchunk *outc, void *modspec)\ {\ intype *ip = inc->buf;\ outtype *op = outc->buf;\ int count = inc->nframes * (chans);\ int i;\ \ preamble;\ for(i=0; i < count; ++i) \ action;\ postamble;\ }\ \ static void name##describe(struct _AFmoduleinst *i)\ {\ _AudioFormat *f = &i->outc->f; \ desc;\ }\ \ static _AFmodule name =\ { \ #name,\ name##describe, \ AF_NULL, AF_NULL, \ _AFsimplemodrun_pull, AF_NULL, AF_NULL, \ _AFsimplemodrun_push, AF_NULL, AF_NULL, \ name##run, \ _AFfreemodspec \ }; #define MODULE(name, desc, intype, outtype, action)\ _MODULE(name, desc, intype, outtype, inc->f.channelCount, \ NULLMODULEPARAM, action, NULLMODULEPARAM) #define MODULEM(name, desc, intype, outtype, modspectype, action)\ _MODULE(name, desc, intype, outtype, inc->f.channelCount, \ modspectype *m = (modspectype *) modspec, action, NULLMODULEPARAM) /* Byte-order-swapping modules. */ #define MODULESWAP(name, type, action) \ MODULE(name, \ f->byteOrder = (f->byteOrder==AF_BYTEORDER_LITTLEENDIAN) ?\ AF_BYTEORDER_BIGENDIAN : AF_BYTEORDER_LITTLEENDIAN,\ type, type,\ action) MODULESWAP(swap2, uchar2, { char3u u; uchar1 c; u.uchar2.s0 = ip[i]; c = u.uchar1.c1; u.uchar1.c1 = u.uchar1.c0; u.uchar1.c0 = c; op[i] = (void *) (u.uchar2.s0); }) MODULESWAP(swap3, real_char3, { char3u u; uchar1 c; u.real_char3_low.c3 = ip[i]; c = u.uchar1.c3; u.uchar1.c3 = u.uchar1.c1; u.uchar1.c1 = c; op[i] = u.real_char3_low.c3; }) MODULESWAP(swap4, uchar4, { char3u u; uchar1 c; u.uchar4.i = ip[i]; c = u.uchar1.c3; u.uchar1.c3 = u.uchar1.c0; u.uchar1.c0 = c; c = u.uchar1.c1; u.uchar1.c1 = u.uchar1.c2; u.uchar1.c2 = c; op[i] = u.uchar4.i; }) MODULESWAP(swap8, real_char8, { real_char8 *i8 = &ip[i]; real_char8 *o8 = &op[i]; o8->c0 = i8->c7; o8->c1 = i8->c6; o8->c2 = i8->c5; o8->c3 = i8->c4; o8->c4 = i8->c3; o8->c5 = i8->c2; o8->c6 = i8->c1; o8->c7 = i8->c0; }) /* modules for dealing with 3-byte integers */ /* convert 0xaabbcc to 0xssaabbcc */ #ifdef WORDS_BIGENDIAN MODULE(real_char3_to_schar3, f /* NOTUSED */, real_char3, schar3, { char3u u; u.real_char3_high.c3 = ip[i]; u.real_char3_high.pad = 0; op[i] = u.schar3.i >> 8; }) #else MODULE(real_char3_to_schar3, f /* NOTUSED */, real_char3, schar3, { char3u u; u.real_char3_low.c3 = ip[i]; u.real_char3_low.pad = 0; op[i] = u.schar3.i >> 8; }) #endif /* convert 0xaabbcc to 0x00aabbcc */ #ifdef WORDS_BIGENDIAN MODULE(real_char3_to_uchar3, f /* NOTUSED */, real_char3, uchar3, { char3u u; u.real_char3_high.c3 = ip[i]; u.real_char3_high.pad = 0; op[i] = u.uchar3.i >> 8; }) #else MODULE(real_char3_to_uchar3, f /* NOTUSED */, real_char3, uchar3, { char3u u; u.real_char3_low.c3 = ip[i]; u.real_char3_low.pad = 0; op[i] = u.uchar3.i >> 8; }) #endif /* convert 0x??aabbcc to 0xaabbcc */ #ifdef WORDS_BIGENDIAN MODULE(char3_to_real_char3, f /* NOTUSED */, uchar3, real_char3, { char3u u; u.uchar3.i = ip[i]; op[i] = u.real_char3_low.c3; }) #else MODULE(char3_to_real_char3, f /* NOTUSED */, uchar3, real_char3, { char3u u; u.uchar3.i = ip[i]; op[i] = u.real_char3_high.c3; }) #endif /* float <--> double ; CASTS */ MODULE(float2double, f->sampleFormat = AF_SAMPFMT_DOUBLE, float, double, op[i] = ip[i] ) MODULE(double2float, f->sampleFormat = AF_SAMPFMT_FLOAT, double, float, op[i] = ip[i] ) /* int2floatN - expects 8N-bit 2's comp ints, outputs floats ; CASTS */ MODULE(int2float1, f->sampleFormat = AF_SAMPFMT_FLOAT, schar1, float, op[i] = ip[i]) MODULE(int2float2, f->sampleFormat = AF_SAMPFMT_FLOAT, schar2, float, op[i] = ip[i]) MODULE(int2float3, f->sampleFormat = AF_SAMPFMT_FLOAT, schar3, float, op[i] = ip[i]) MODULE(int2float4, f->sampleFormat = AF_SAMPFMT_FLOAT, schar4, float, op[i] = ip[i]) /* int2doubleN - expects 8N-bit 2's comp ints, outputs doubles ; CASTS */ MODULE(int2double1, f->sampleFormat = AF_SAMPFMT_DOUBLE, schar1, double, op[i] = ip[i]) MODULE(int2double2, f->sampleFormat = AF_SAMPFMT_DOUBLE, schar2, double, op[i] = ip[i]) MODULE(int2double3, f->sampleFormat = AF_SAMPFMT_DOUBLE, schar3, double, op[i] = ip[i]) MODULE(int2double4, f->sampleFormat = AF_SAMPFMT_DOUBLE, schar4, double, op[i] = ip[i]) /* The following modules perform the transformation between one pcm mapping and another. The modules all use MODULETRANS; some of them also perform clipping. Use initpcmmod() to create an instance of any of these modules. initpcmmod() takes an _PCMInfo describing the desired output pcm mapping. */ typedef struct pcmmodspec { /* These are the computed parameters of the transformation. */ double m, b; double maxv, minv; /* This is what goes in i->outc->f. */ _PCMInfo output_mapping; } pcmmodspec; /* initpcmmod */ static _AFmoduleinst initpcmmod (_AFmodule *mod, _PCMInfo *input_mapping, _PCMInfo *output_mapping) { _AFmoduleinst ret = _AFnewmodinst(mod); pcmmodspec *m = _af_malloc(sizeof (pcmmodspec)); ret.modspec = m; /* Remember output mapping for use in the describe function. */ m->output_mapping = *output_mapping; /* Compute values needed to perform transformation if the module being initialized does a transformation.. */ if (input_mapping) { m->m = output_mapping->slope / input_mapping->slope; m->b = output_mapping->intercept - m->m * input_mapping->intercept; } /* Remember clip values. */ m->minv = output_mapping->minClip; m->maxv = output_mapping->maxClip; return ret; } #define MODULETRANS( name, xtradesc, intype, outtype, action ) \ MODULEM(name, \ { \ f->pcm = ((pcmmodspec *) i->modspec)->output_mapping; \ xtradesc; \ }, \ intype, outtype, pcmmodspec, \ action) MODULETRANS(floattransform, NULLMODULEPARAM, float, float, \ op[i]=(m->b + m->m * ip[i])) MODULETRANS(doubletransform, NULLMODULEPARAM, double, double, \ op[i]=(m->b + m->m * ip[i])) /* float2intN_clip - expects floats, outputs CLIPped, 8N-bit, transformed 2's comp ints double2intN_clip - same deal with doubles */ #define TRANS_CLIP(type) \ {\ double d=(m->b + m->m * ip[i]); \ op[i] = \ (((type)((d>(m->maxv)) ? (m->maxv) : ((d<(m->minv))?(m->minv):d)))); \ } MODULETRANS(float2int1_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 8; }, float, schar1, TRANS_CLIP(schar1)) MODULETRANS(float2int2_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 16; }, float, schar2, TRANS_CLIP(schar2)) MODULETRANS(float2int3_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 24; }, float, schar3, TRANS_CLIP(schar3)) MODULETRANS(float2int4_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 32; }, float, schar4, TRANS_CLIP(schar4)) MODULETRANS(double2int1_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 8; }, double, schar1, TRANS_CLIP(schar1)) MODULETRANS(double2int2_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 16; }, double, schar2, TRANS_CLIP(schar2)) MODULETRANS(double2int3_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 24; }, double, schar3, TRANS_CLIP(schar3)) MODULETRANS(double2int4_clip, { f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->sampleWidth = 32; }, double, schar4, TRANS_CLIP(schar4)) /* clipping modules - use initpcmmod() to make one of these clips to range given as argument to init function. */ #define MODULECLIP(name, type)\ MODULEM(name, \ { f->pcm = ((pcmmodspec *)i->modspec)->output_mapping; }, \ type, type, pcmmodspec, \ { \ type d=ip[i]; \ type min=(type)(m->minv); \ type max=(type)(m->maxv); \ op[i] = ((d>max) ? max : ((dsampleFormat = AF_SAMPFMT_TWOSCOMP; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, uchar1, schar1, op[i] = ip[i] + MIN_INT8) MODULE(unsigned2signed2, { double shift = (double) MIN_INT16; f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, uchar2, schar2, op[i] = ip[i] + MIN_INT16) MODULE(unsigned2signed3, { double shift = (double) MIN_INT24; f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, uchar3, schar3, op[i] = ip[i] + MIN_INT24) MODULE(unsigned2signed4, { double shift = (double) MIN_INT32; f->sampleFormat = AF_SAMPFMT_TWOSCOMP; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, uchar4, schar4, op[i] = ip[i] + MIN_INT32) /* !! unsigned2signed4 shouldn't work, but it does !! */ /* signed2unsignedN - expects 8N-bit 2's comp ints, outputs unsigned */ MODULE(signed2unsigned1, { double shift = -(double) MIN_INT8; f->sampleFormat = AF_SAMPFMT_UNSIGNED; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, schar1, uchar1, op[i] = ip[i] - MIN_INT8) MODULE(signed2unsigned2, { double shift = -(double) MIN_INT16; f->sampleFormat = AF_SAMPFMT_UNSIGNED; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, schar2, uchar2, op[i] = ip[i] - MIN_INT16) MODULE(signed2unsigned3, { double shift = -(double) MIN_INT24; f->sampleFormat = AF_SAMPFMT_UNSIGNED; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, schar3, uchar3, op[i] = ip[i] - MIN_INT24) MODULE(signed2unsigned4, { double shift = -(double) MIN_INT32; f->sampleFormat = AF_SAMPFMT_UNSIGNED; f->pcm.intercept += shift; f->pcm.minClip += shift; f->pcm.maxClip += shift; }, schar4, uchar4, op[i] = ip[i] - MIN_INT32) /* !! signed2unsigned4 shouldn't work, but it does !! */ /* These convert between different 2's complement integer formats with no roundoff/asymmetric errors. They should also work faster than converting integers to floats and back to other integers. They are only meant to be used when the input and output integers have the default PCM mapping; otherwise, arrangemodules will make the conversion go through floating point and these modules will not be used. */ #define intmap _af_default_signed_integer_pcm_mappings /* shorthand */ MODULE(int1_2, { f->sampleWidth = 16; f->pcm=intmap[2]; }, schar1, schar2, op[i] = ip[i] << 8) MODULE(int1_3, { f->sampleWidth = 24; f->pcm=intmap[3]; }, schar1, schar3, op[i] = ip[i] << 16) MODULE(int1_4, { f->sampleWidth = 32; f->pcm=intmap[4]; }, schar1, schar4, op[i] = ip[i] << 24) MODULE(int2_1, { f->sampleWidth = 8; f->pcm=intmap[1]; }, schar2, schar1, op[i] = ip[i] >> 8) MODULE(int2_3, { f->sampleWidth = 24; f->pcm=intmap[3]; }, schar2, schar3, op[i] = ip[i] << 8) MODULE(int2_4, { f->sampleWidth = 32; f->pcm=intmap[4]; }, schar2, schar4, op[i] = ip[i] << 16) MODULE(int3_1, { f->sampleWidth = 8; f->pcm=intmap[1]; }, schar3, schar1, op[i] = ip[i] >> 16) MODULE(int3_2, { f->sampleWidth = 16; f->pcm=intmap[2]; }, schar3, schar2, op[i] = ip[i] >> 8) MODULE(int3_4, { f->sampleWidth = 32; f->pcm=intmap[4]; }, schar3, schar4, op[i] = ip[i] << 8) MODULE(int4_1, { f->sampleWidth = 8; f->pcm=intmap[1]; }, schar4, schar1, op[i] = ip[i] >> 24) MODULE(int4_2, { f->sampleWidth = 16; f->pcm=intmap[2]; }, schar4, schar2, op[i] = ip[i] >> 16) MODULE(int4_3, { f->sampleWidth = 24; f->pcm=intmap[3]; }, schar4, schar3, op[i] = ip[i] >> 8) #undef intmap /* channel changer modules - convert channels using channel matrix The channel matrix is a two-dimensional array of doubles, the rows of which correspond to the virtual format, and the columns of which correspond to the file format. If the channel matrix is null (unspecified), then the default behavior occurs (see initchannelchange). Internally, the module holds a copy of the matrix in which the rows correspond to the output format, and the columns correspond to the input format (therefore, if reading==AF_FALSE, the matrix is transposed as it is copied). */ typedef struct channelchangedata { int outchannels; double minClip; double maxClip; double *matrix; } channelchangedata; /* channelchangefree */ static void channelchangefree (struct _AFmoduleinst *i) { channelchangedata *d = i->modspec; assert(d); assert(d->matrix); free(d->matrix); free(d); i->modspec = AF_NULL; } /* channelchangedescribe */ static void channelchangedescribe (struct _AFmoduleinst *i) { channelchangedata *m = (channelchangedata *) i->modspec; i->outc->f.channelCount = m->outchannels; i->outc->f.pcm.minClip = m->minClip; i->outc->f.pcm.maxClip = m->maxClip; } #define CHANNELMOD( name, type, zero_op, action, afteraction ) \ static void name##run(_AFchunk *inc, _AFchunk *outc, void *modspec) \ { \ type *ip = inc->buf; \ type *op = outc->buf; \ double *matrix = ((channelchangedata *)modspec)->matrix; \ double *m; \ int frame, inch, outch; \ \ for (frame=0; frame < outc->nframes; frame++) \ { \ type *ipsave; \ \ m = matrix; \ ipsave = ip; \ \ for (outch = 0; outch < outc->f.channelCount; outch++) \ { \ zero_op; \ ip = ipsave; \ \ for (inch = 0; inch < inc->f.channelCount; inch++) \ action;\ \ afteraction; \ op++;\ }\ }\ }\ \ static _AFmodule name =\ { \ #name, \ channelchangedescribe, \ AF_NULL, AF_NULL, \ _AFsimplemodrun_pull, AF_NULL, AF_NULL, \ _AFsimplemodrun_push, AF_NULL, AF_NULL, \ name##run, \ channelchangefree \ }; CHANNELMOD(channelchangefloat, float, *op = 0.0, *op += *ip++ * *m++, \ NULLMODULEPARAM) CHANNELMOD(channelchangedouble, double, *op = 0.0, *op += *ip++ * *m++, \ NULLMODULEPARAM) #define CHANNELINTMOD(name, type) \ CHANNELMOD(name, type, \ double d=0.0, \ d += *ip++ * *m++, \ { \ double minv=outc->f.pcm.minClip; \ double maxv=outc->f.pcm.maxClip; \ *op = (type) ((d>maxv) ? maxv : ((doutchannels = outchannels; d->minClip = outpcm->minClip; d->maxClip = outpcm->maxClip; d->matrix = _af_malloc(sizeof (double) * inchannels * outchannels); /* Set d->matrix to a default matrix if a matrix was not specified. */ if (!matrix) { bool special=AF_FALSE; /* Handle many common special cases. */ if (inchannels==1 && outchannels==2) { static double m[]={1,1}; matrix=m; special=AF_TRUE; } else if (inchannels==1 && outchannels==4) { static double m[]={1,1,0,0}; matrix=m; special=AF_TRUE; } else if (inchannels==2 && outchannels==1) { static double m[]={.5,.5}; matrix=m; special=AF_TRUE; } else if (inchannels==2 && outchannels==4) { static double m[]={1,0,0,1,0,0,0,0}; matrix=m; special=AF_TRUE; } else if (inchannels==4 && outchannels==1) { static double m[]={.5,.5,.5,.5}; matrix=m; special=AF_TRUE; } else if (inchannels==4 && outchannels==2) { static double m[]={1,0,1,0,0,1,0,1}; matrix=m; special=AF_TRUE; } else { /* Each input channel from 1 to N maps to output channel 1 to N where N=min(inchannels, outchannels). */ for(i=0; i < inchannels; i++) for(j=0; j < outchannels; j++) d->matrix[j*inchannels + i] = (i==j) ? 1.0 : 0.0; } if (special) memcpy(d->matrix, matrix, sizeof (double) * inchannels * outchannels); } /* Otherwise transfer matrix into d->matrix. */ else { /* reading: copy matrix */ if (reading) { memcpy(d->matrix, matrix, sizeof (double) * inchannels * outchannels); } /* writing: transpose matrix */ else { for (i=0; i < inchannels; i++) for (j=0; j < outchannels; j++) d->matrix[j*inchannels + i] = matrix[i*outchannels + j]; } } DEBG(printf("channelchange d->matrix=")); DEBG(_af_print_channel_matrix(d->matrix, inchannels, outchannels)); DEBG(printf("\n")); return(ret); } /* just used here */ typedef struct current_state { _AFmoduleinst *modinst; /* current mod instance we're creating */ _AFchunk *inchunk; /* current input chunk */ _AFchunk *outchunk; /* current output chunk */ } current_state; /* addmod is called once per added module instance. It does the work of putting the module instance in the list and assigning it an input and output chunk. */ static void addmod (current_state *current, _AFmoduleinst modinst) { *(current->modinst) = modinst; current->modinst->valid = AF_TRUE; /* at this point mod must be valid */ /* Assign the new module instance an input and an output chunk. */ current->modinst->inc = current->inchunk; current->modinst->outc = current->outchunk; /* The output chunk has the same format and number of frames as input chunk, except in whatever way the 'describe' method tells us (see README.modules). */ *(current->outchunk) = *(current->inchunk); if (current->modinst->mod->describe) (*current->modinst->mod->describe)(current->modinst); /* Advance to next module and next chunks. Note that next moduleinst will have this module's out chunk as input. */ current->modinst++; current->inchunk = current->outchunk; current->outchunk++; } /* initfilemods: Functions that deal with extended-lifetime file read / file write modules and their extended-lifetime rebuffer modules. called once in the lifetime of an AFfilehandle. If h->access == _AF_READ_ACCESS: Create the module which will be the first module in the chain, the one which reads the file. This module does the decompression if necessary, or it could just be a PCM file reader. If h->access == _AF_WRITE_ACCESS: Create the module which will be the last module in the chain, the one which writes the file. This module does the compression if necessary, or it could just be a PCM file writer. Also creates a rebuffer module for these modules if necessary. */ static status initfilemods (_Track *track, AFfilehandle h) { int compressionIndex; _CompressionUnit *compunit; AFframecount chunkframes; compressionIndex = _af_compression_index_from_id(track->f.compressionType); compunit = &_af_compression[compressionIndex]; /* Invalidate everything. */ track->ms.filemodinst.valid = AF_FALSE; track->ms.filemod_rebufferinst.valid = AF_FALSE; /* Seek to beginning of sound data in the track. This is needed ONLY for those modules which have to read/write some kind of pre-data header or table in the sound data chunk of the file (such as aware). This is NOT the seek that sets the file at the beginning of the data. */ /* XXXmpruett -- we currently don't set seekok. if (h->seekok && af_fseek(h->fh, track->fpos_first_frame, SEEK_SET) < 0) */ if (af_fseek(h->fh, track->fpos_first_frame, SEEK_SET) < 0) { _af_error(AF_BAD_LSEEK, "unable to position file handle at beginning of sound data"); return AF_FAIL; } /* Create file read/write module. */ track->filemodhappy = AF_TRUE; if (h->access == _AF_READ_ACCESS) track->ms.filemodinst = (*compunit->initdecompress)(track, h->fh, h->seekok, (h->fileFormat==AF_FILE_RAWDATA), &chunkframes); else track->ms.filemodinst = (*compunit->initcompress)(track, h->fh, h->seekok, (h->fileFormat==AF_FILE_RAWDATA), &chunkframes); if (!track->filemodhappy) return AF_FAIL; track->ms.filemodinst.valid = AF_TRUE; /* WHEN DOES THE FILE GET LSEEKED ? Somebody sometime has got to lseek the file to the beginning of the audio data. Similarly, somebody has to lseek the file at the time of reset or sync. Furthermore, we have to make sure that we operate correctly if more than one track is being read or written. This is handled differently based on whether we are reading or writing, and whether the ONE_TRACK_ONLY lseek optimization is engaged. READING: If we are reading, the file needs to be positioned once before we start reading and then once per seek. If ONE_TRACK_ONLY is not defined, then there can be multiple tracks in the file. Thus any call to afReadFrames could cause the file pointer to be put anywhere: we can not rely on the file pointer tracking only one track in the file, thus we must seek to the current position in track N whenever we begin an AFreadframes on track N. Thus the lseek is done in afReadFrames. When a reset occurs (including the initial one), we merely set trk->fpos_next_frame, and the next afReadFrames will seek the file there before proceeding. If ONE_TRACK_ONLY is defined, meaning there can only be 1 track in the file, we do not need to ever seek during normal sequential operation, because the file read module is the only module which ever accesses the file after _afOpenFile returns. In this case, we do not need to do the expensive lseek at the beginning of every AFreadframes call. We need only seek once when the file is first opened and once when the file is seeked. At both of these times, we reset the modules. So we can do the lseek in resetmodules() right after it has called all of the modules' reset2 methods. WRITING: If we are writing, the file needs to be positioned once before we start writing and it needs to be positioned after every complete sync operation on the file. If ONE_TRACK_ONLY is not defined, then there can be multiple tracks in the file. This assumes space for the tracks has been preallocated. Thus any call to AFwriteframes could cause the file pointer to be put anywhere: we can not rely on the file pointer tracking only one track in the file, thus we must seek to the current position in track n whenever we begin an AFwriteframes on track n. Thus the lseek is done in AFwriteframes. When we first start, and when a sync occurs, we merely set trk->fpos_next_frame, and the next AFwriteframes will seek the file there before proceeding. If ONE_TRACK_ONLY is defined, meaning there can only be 1 track in the file, we do not need to ever seek during normal sequential operation, because the file write module is the only module which ever accesses the file after _AFopenfile returns. In this case, we do not need to do the expensive lseek at the beginning of every AFwriteframes call. We can do the lseek for the initial case right here (that's what you see below), and we can do the lseek for syncs in _AFsyncmodules right after it has called all of the modules' sync2 methods. One annoying exceptional case is _AFeditrate, which can get called at any time. But it saves and restores the file position at its beginning and end, so it's no problem. WHY THE F(*#&@ DON'T YOU JUST HAVE MULTIPLE FILE DESCRIPTORS? The obviously and blatantly better way to do this would be to simply open one fd per track and then all the problems go away! Too bad we offer afOpenFD() in the API, which makes it impossible for the AF to get more than 1 fd for the file. afOpenFD() is unfortunately used far more often than afOpenFile(), so the benefit of doing the optimization the "right" way in the cases where afOpenFile() are used are not currently too great. But one day we will have to phase out afOpenFD(). Too bad, it seemed like such a great idea when we put it in. */ #ifdef ONE_TRACK_ONLY if (h->access == _AF_WRITE_ACCESS) { if (h->seekok && af_fseek(h->fh, track->fpos_next_frame, SEEK_SET) < 0) { _af_error(AF_BAD_LSEEK, "unable to position write ptr at first data frame"); return AF_FAIL; } } #endif /* Create its rebuffer module. */ if (compunit->needsRebuffer) { /* We assume the following for now. */ assert(compunit->nativeSampleFormat == AF_SAMPFMT_TWOSCOMP); assert(compunit->nativeSampleWidth == 16); if (h->access == _AF_WRITE_ACCESS) track->ms.filemod_rebufferinst = _af_initint2rebufferv2f(chunkframes*track->f.channelCount, compunit->multiple_of); else track->ms.filemod_rebufferinst = _af_initint2rebufferf2v(chunkframes*track->f.channelCount, compunit->multiple_of); track->ms.filemod_rebufferinst.valid = AF_TRUE; } else track->ms.filemod_rebufferinst.valid = AF_FALSE; /* These modules should not get freed until the file handle is destroyed (i.e. the file is closed). */ track->ms.filemodinst.free_on_close = AF_TRUE; track->ms.filemod_rebufferinst.free_on_close = AF_TRUE; return AF_SUCCEED; } /* addfilereadmods: called once per setup of the modules for a given AFfilehandle */ static status addfilereadmods (current_state *current, _Track *track, AFfilehandle h) { assert(track->ms.filemodinst.valid); /* Fail in case code is broken and NDEBUG is defined. */ if (!track->ms.filemodinst.valid) return AF_FAIL; addmod(current, track->ms.filemodinst); if (track->ms.filemod_rebufferinst.valid) addmod(current, track->ms.filemod_rebufferinst); return AF_SUCCEED; } /* addfilewritemods is called once per setup of the modules for a given AFfilehandle. */ static status addfilewritemods (current_state *current, _Track *track, AFfilehandle h) { assert(track->ms.filemodinst.valid); /* Fail in case code is broken and NDEBUG is defined. */ if (!track->ms.filemodinst.valid) return(AF_FAIL); if (track->ms.filemod_rebufferinst.valid) addmod(current, track->ms.filemod_rebufferinst); addmod(current, track->ms.filemodinst); return(AF_SUCCEED); } /* disposefilemods: called once in the lifetime of an AFfilehandle */ static status disposefilemods (_Track *track) { if (track->ms.filemodinst.valid && track->ms.filemodinst.mod->free) (*track->ms.filemodinst.mod->free)(&track->ms.filemodinst); track->ms.filemodinst.valid = AF_FALSE; if (track->ms.filemod_rebufferinst.valid && track->ms.filemod_rebufferinst.mod->free) (*track->ms.filemod_rebufferinst.mod->free)(&track->ms.filemod_rebufferinst); track->ms.filemod_rebufferinst.valid = AF_FALSE; return AF_SUCCEED; } /* useAP: rate conversion AP decision maker and warner and kludger */ static bool useAP (double inrate, double outrate, double *inratep, double *outratep) { bool instandard = (inrate==8000 || inrate==11025 || inrate==16000 || inrate==22050 || inrate==32000 || inrate==44100 || inrate==48000); bool outstandard = (outrate==8000 || outrate==11025 || outrate==16000 || outrate==22050 || outrate==32000 || outrate==44100 || outrate==48000); bool incodec; bool outcodec; incodec = (inrate==_AF_SRATE_CODEC || inrate==(long)_AF_SRATE_CODEC); outcodec = (outrate==_AF_SRATE_CODEC || outrate==(long)_AF_SRATE_CODEC); *inratep = inrate; *outratep = outrate; if (instandard && outstandard) return AF_TRUE; if (incodec && outstandard && outrate != 8000.00) { _af_error(AF_WARNING_CODEC_RATE, "WARNING using input rate 8 kHz instead of %.30g Hz " "to allow high-quality rate conversion", inrate); *inratep = 8000.00; return AF_TRUE; } if (instandard && inrate != 8000.00 && outcodec) { _af_error(AF_WARNING_CODEC_RATE, "WARNING using output rate 8 kHz instead of %.30g Hz " "to allow high-quality rate conversion", outrate); *outratep = 8000.00; return AF_TRUE; } if (!instandard && !outstandard) _af_error(AF_WARNING_RATECVT, "WARNING using lower quality rate conversion due to " "rates %.30g and %.30g -- " "output file may contain audible artifacts", inrate, outrate); else if (!instandard) _af_error(AF_WARNING_RATECVT, "WARNING using lower quality rate conversion due to " "input rate %.30g -- " "output file may contain audible artifacts", inrate); else /* !outstandard */ _af_error(AF_WARNING_RATECVT, "WARNING using lower quality rate conversion due to " "output rate %.30g -- " "output file may contain audible artifacts", outrate); return AF_FALSE; } /* initrateconvertmods handles the extended-life rate conversion module and its extended-life rebuffer module called once in the lifetime of an AFfilehandle. */ static void initrateconvertmods (bool reading, _Track *track) { /* no rate conversion initially */ track->ms.rateconvertinst.valid = AF_FALSE; track->ms.rateconvert_rebufferinst.valid = AF_FALSE; } static void disposerateconvertmods (_Track *); /* XXXmpruett rate conversion is disabled for now */ #if 0 /* addrateconvertmods: called once per setup of the modules for a given AFfilehandle */ static void addrateconvertmods (current_state *current, int nchannels, double inrate, double outrate, bool reading, _Track *track) { AFframecount inframes, outframes; /* Check if we are no longer rate converting. */ if (inrate == outrate) { disposerateconvertmods(track); track->ratecvt_filter_params_set = AF_FALSE; /* XXX HACK */ } else { /* We need new rateconverter if we didn't have one or if rate has changed or rate conversion params have changed. */ if (!track->ms.rateconvertinst.valid || inrate != track->ms.rateconvert_inrate || outrate != track->ms.rateconvert_outrate || track->ratecvt_filter_params_set /* HACK */) { bool usingAP = useAP(inrate, outrate, &inrate, &outrate); disposerateconvertmods(track); track->ratecvt_filter_params_set = AF_FALSE; /* HACK */ if (usingAP) { track->ms.rateconvertinst = InitAFRateConvert(inrate, outrate, nchannels, track->taper, track->dynamic_range, &inframes, &outframes, track, reading); if (!reading) track->ms.rateconvert_rebufferinst = initfloatrebufferv2f(inframes*nchannels, AF_FALSE); else track->ms.rateconvert_rebufferinst = initfloatrebufferf2v(outframes*nchannels, AF_FALSE); track->ms.rateconvertinst.valid = AF_TRUE; track->ms.rateconvert_rebufferinst.valid = AF_TRUE; } else { track->ms.rateconvertinst = initpolyratecvt(track, inrate, outrate, nchannels, reading); track->ms.rateconvertinst.valid = AF_TRUE; track->ms.rateconvert_rebufferinst.valid = AF_FALSE; } track->ms.rateconvert_inrate = inrate; track->ms.rateconvert_outrate = outrate; track->ms.rateconvertinst.free_on_close = AF_TRUE; track->ms.rateconvert_rebufferinst.free_on_close = AF_TRUE; } /* Add the rate conversion modules. */ if (!reading && track->ms.rateconvert_rebufferinst.valid) addmod(current, track->ms.rateconvert_rebufferinst); addmod(current, track->ms.rateconvertinst); if (reading && track->ms.rateconvert_rebufferinst.valid) addmod(current, track->ms.rateconvert_rebufferinst); } } /* disposerateconvertmods is called once in the lifetime of an AFfilehandle. */ static void disposerateconvertmods (_Track *track) { /* Neither module is necessarily valid--there could have been an error, or the modules could possibly never have been set up. */ if (track->ms.rateconvertinst.valid && track->ms.rateconvertinst.mod->free) { (*track->ms.rateconvertinst.mod->free) (&track->ms.rateconvertinst); } track->ms.rateconvertinst.valid = AF_FALSE; if (track->ms.rateconvert_rebufferinst.valid && track->ms.rateconvert_rebufferinst.mod->free) { (*track->ms.rateconvert_rebufferinst.mod->free) (&track->ms.rateconvert_rebufferinst); } track->ms.rateconvert_rebufferinst.valid = AF_FALSE; } #endif /* XXXmpruett rate conversion is disabled for now */ /* -------------------------------------------------------------- */ /* The stuff in this section is used by arrangemodules(). */ static _AFmodule *unsigned2signed[5] = { NULL, &unsigned2signed1, &unsigned2signed2, &unsigned2signed3, &unsigned2signed4 }; static _AFmodule *signed2unsigned[5] = { NULL, &signed2unsigned1, &signed2unsigned2, &signed2unsigned3, &signed2unsigned4 }; static _AFmodule *swapbytes[9] = { NULL, NULL, &swap2, &swap3, &swap4, NULL, NULL, NULL, &swap8 }; /* don't forget int24_fmt is really 24 bits right-justified in 32 bits */ typedef enum format_code { int8_fmt, int16_fmt, int24_fmt, int32_fmt, float_fmt, double_fmt } format_code; #define isinteger(fc) ((fc) <= int32_fmt) #define isfloating(fc) ((fc) >= float_fmt) /* get_format_code */ static format_code get_format_code (_AudioFormat *fmt) { if (fmt->sampleFormat == AF_SAMPFMT_FLOAT) return float_fmt; if (fmt->sampleFormat == AF_SAMPFMT_DOUBLE) return double_fmt; if (fmt->sampleFormat == AF_SAMPFMT_TWOSCOMP || fmt->sampleFormat == AF_SAMPFMT_UNSIGNED) { switch (_af_format_sample_size_uncompressed(fmt, AF_FALSE)) { case 1: return int8_fmt; case 2: return int16_fmt; case 3: return int24_fmt; case 4: return int32_fmt; } } /* NOTREACHED */ assert(0); return -1; } static _AFmodule *to_flt[6] = { &int2float1, &int2float2, &int2float3, &int2float4, NULL, &double2float }; static _AFmodule *to_dbl[6] = { &int2double1, &int2double2, &int2double3, &int2double4, &float2double, NULL }; static _AFmodule *clip[6] = { &clip1, &clip2, &clip3, &clip4, &clipfloat, &clipdouble }; static _AFmodule *channelchanges[6] = { &channelchange1, &channelchange2, &channelchange3, &channelchange4, &channelchangefloat, &channelchangedouble }; /* indices are of type format_code: matrix[infmtcode][outfmtcode] */ static _AFmodule *convertmatrix[6][6] = { /* TO: { int8_fmt, int16_fmt, int24_fmt, int32_fmt, float_fmt, double_fmt } */ /* FROM int8_fmt */ { NULL, &int1_2, &int1_3, &int1_4, &int2float1, &int2double1 }, /* FROM int16_fmt */ { &int2_1, NULL, &int2_3, &int2_4, &int2float2, &int2double2 }, /* FROM int24_fmt */ { &int3_1, &int3_2, NULL, &int3_4, &int2float3, &int2double3 }, /* FROM int32_fmt */ { &int4_1, &int4_2, &int4_3, NULL, &int2float4, &int2double4 }, /* FROM float_fmt */ { &float2int1_clip, &float2int2_clip, &float2int3_clip, &float2int4_clip, NULL, &float2double }, /* FROM double_fmt */ { &double2int1_clip, &double2int2_clip, &double2int3_clip, &double2int4_clip, &double2float, NULL } }; static _PCMInfo *intmappings[6] = { &_af_default_signed_integer_pcm_mappings[1], &_af_default_signed_integer_pcm_mappings[2], &_af_default_signed_integer_pcm_mappings[3], &_af_default_signed_integer_pcm_mappings[4], AF_NULL, AF_NULL }; /* trivial_int_clip */ static bool trivial_int_clip (_AudioFormat *f, format_code code) { return (intmappings[code] != NULL && f->pcm.minClip == intmappings[code]->minClip && f->pcm.maxClip == intmappings[code]->maxClip); } /* trivial_int_mapping */ static bool trivial_int_mapping (_AudioFormat *f, format_code code) { return (intmappings[code] != NULL && f->pcm.slope == intmappings[code]->slope && f->pcm.intercept == intmappings[code]->intercept); } /* arrangemodules decides which modules to use and creates instances of them. */ static status arrangemodules (_AFfilehandle *h, _Track *track) { bool reading = (h->access == _AF_READ_ACCESS); current_state current; bool rateconverting, transforming; bool already_clipped_output, already_transformed_output; int insampbytes, outsampbytes; int chans; format_code infc, outfc; /* in and out are the formats at the start and end of the chain of modules, respectively. */ _AudioFormat in, out; /* in==FILE, out==virtual (user) */ if (reading) { in = track->f; out = track->v; } /* in==virtual (user), out==FILE */ else { in = track->v; out = track->f; } infc = get_format_code(&in); outfc = get_format_code(&out); /* flags */ rateconverting = (in.sampleRate != out.sampleRate); /* throughout routine: current.modinst points to current module current.inchunk points to current in chunk, always outchunk-1 current.outchunk points to current out chunk The addmod() function does most of the work. It calls the "describe" module function, during which a module looks at inc->f and writes the format it will output in outc->f. */ current.modinst = track->ms.module; current.inchunk = track->ms.chunk; current.outchunk = track->ms.chunk + 1; current.inchunk->f = in; /* max # of modules that could be needed together may need to change this if you change this function */ #define MAX_MODULES 17 /* Actually arrange the modules. Call addmod() to add one. */ /* Add file reader and possibly a decompressor. */ if (reading) if (AF_FAIL == addfilereadmods(¤t, track, h)) return AF_FAIL; /* Make data native-endian. */ if (in.byteOrder != _AF_BYTEORDER_NATIVE) { int bytes_per_samp = _af_format_sample_size_uncompressed(&in, !reading); if (bytes_per_samp > 1 && in.compressionType == AF_COMPRESSION_NONE) { assert(swapbytes[bytes_per_samp]); addmod(¤t, _AFnewmodinst(swapbytes[bytes_per_samp])); } else in.byteOrder = _AF_BYTEORDER_NATIVE; } /* Handle nasty 3-byte input cases. */ insampbytes = _af_format_sample_size_uncompressed(&in, AF_FALSE); if (isinteger(infc) && insampbytes == 3) { if (reading || in.compressionType != AF_COMPRESSION_NONE) { /* We're reading 3-byte ints from a file. At this point stretch them to 4-byte ints by sign-extending or adding a zero-valued most significant byte. We could also be reading/writing 3-byte samples output from a decompressor. */ if (in.sampleFormat == AF_SAMPFMT_UNSIGNED) addmod(¤t, _AFnewmodinst(&real_char3_to_uchar3)); else addmod(¤t, _AFnewmodinst(&real_char3_to_schar3)); } else /* writing, non-compressed */ { /* We're processing 3-byte ints from the user, which come in as sign-extended 4-byte quantities. How convenient: this is what we want. */ } } /* Make data signed. */ if (in.sampleFormat == AF_SAMPFMT_UNSIGNED) { addmod(¤t, _AFnewmodinst(unsigned2signed[insampbytes])); } /* Standardize pcm mapping of "in" and "out". */ /* Since they are used to compute transformations in the inner section of this routine (inside of sign conversion), we need in.pcm and out.pcm in terms of AF_SAMPFMT_TWOSCOMP numbers. */ in.pcm = current.inchunk->f.pcm; /* "in" is easy */ if (out.sampleFormat == AF_SAMPFMT_UNSIGNED) /* "out": undo the unsigned shift */ { double shift = intmappings[outfc]->minClip; out.pcm.intercept += shift; out.pcm.minClip += shift; out.pcm.maxClip += shift; } /* ------ CLIP user's input samples if necessary */ if (in.pcm.minClip < in.pcm.maxClip && !trivial_int_clip(&in, infc)) addmod(¤t, initpcmmod(clip[infc], AF_NULL, &in.pcm)); /* At this point, we assume we can have doubles, floats, and 1-, 2-, and 4-byte signed integers on the input and on the output (or 4-byte integers with 24 significant (low) bits, int24_fmt). Now we handle rate conversion and pcm transformation. */ /* If rate conversion will happen, we must have floats. */ /* This may result in loss of precision. This bug must be fixed eventually. */ if (rateconverting && infc != float_fmt) { addmod(¤t, _AFnewmodinst(to_flt[infc])); infc = float_fmt; } /* We must make sure the output samples will get clipped to SOMETHING reasonable if we are rateconverting. The user cannot possibly expect to need to clip values just because rate conversion is on. */ if (out.pcm.minClip >= out.pcm.maxClip && rateconverting) { out.pcm.minClip = out.pcm.intercept - out.pcm.slope; out.pcm.maxClip = out.pcm.intercept + out.pcm.slope; } already_clipped_output = AF_FALSE; already_transformed_output = AF_FALSE; /* We need to perform a transformation (in floating point) if the input and output PCM mappings are different. The only exceptions are the trivial integer conversions (i.e., full-range integers of one # of bytes to full-range integers to another # of bytes). */ transforming = (in.pcm.slope != out.pcm.slope || in.pcm.intercept != out.pcm.intercept) && !(trivial_int_mapping(&in, infc) && trivial_int_mapping(&out,outfc)); /* If we have ints on input and the user is performing a change of mapping other than a trivial one, we must go to floats or doubles. */ if (isinteger(infc) && transforming) { /* Use doubles if either the in or out format has that kind of precision. */ if (infc == int32_fmt || outfc == double_fmt || outfc == int32_fmt) { addmod(¤t, _AFnewmodinst(to_dbl[infc])); infc = double_fmt; } else { addmod(¤t, _AFnewmodinst(to_flt[infc])); infc = float_fmt; } } DEBG(printf("arrangemodules in="); _af_print_audioformat(&in);); DEBG(printf("arrangemodules out="); _af_print_audioformat(&out);); DEBG(printf("arrangemodules transforming=%d\n", transforming)); DEBG(printf("arrangemodules infc=%d outfc=%d\n", infc, outfc)); /* invariant: At this point, if infc is an integer format, then we are not rate converting, nor are we perfoming any change of mapping other than possibly a trivial int->int conversion. */ /* ----- convert format infc to format outfc */ /* change channels if appropriate now */ if (in.channelCount != out.channelCount && (infc > outfc || (infc==outfc && out.channelCount < in.channelCount))) { addmod(¤t, initchannelchange(channelchanges[infc], track->channelMatrix, &in.pcm, in.channelCount, out.channelCount, reading)); chans = out.channelCount; } else chans = in.channelCount; /* Transform floats if appropriate now. */ if (transforming && infc==double_fmt && isfloating(outfc)) { addmod(¤t, initpcmmod(&doubletransform, &in.pcm, &out.pcm)); } #if 0 /* XXXmpruett */ /* Handle rate conversion (will do the right thing if not rate converting). */ addrateconvertmods(¤t, chans, in.sampleRate, out.sampleRate, reading, track); #endif /* Add format conversion, if needed */ if (convertmatrix[infc][outfc]) { /* for float/double -> int conversions, the module we use here also does the transformation and clipping. We use initpcmmod() in any case because it is harmless for the other modules in convertmatrix[][]. */ if (isfloating(infc) && isinteger(outfc)) /* "float"->"int" */ { already_clipped_output = AF_TRUE; already_transformed_output = AF_TRUE; } addmod(¤t, initpcmmod(convertmatrix[infc][outfc], &in.pcm, &out.pcm)); } /* Transform floats if appropriate now. */ if (transforming && !already_transformed_output && infc != double_fmt) { if (outfc==double_fmt) addmod(¤t, initpcmmod(&doubletransform, &in.pcm, &out.pcm)); else if (outfc==float_fmt) addmod(¤t, initpcmmod(&floattransform, &in.pcm, &out.pcm)); } /* Change channels if appropriate now. */ if (in.channelCount != out.channelCount && (outfc > infc || (infc==outfc && in.channelCount < out.channelCount))) { addmod(¤t, initchannelchange(channelchanges[outfc], track->channelMatrix, &out.pcm, in.channelCount, out.channelCount, reading)); } /* ------ CLIP user's output samples if needed */ if (!already_clipped_output) { if (out.pcm.minClip < out.pcm.maxClip && !trivial_int_clip(&out, outfc)) { addmod(¤t, initpcmmod(clip[outfc], NULL, &out.pcm)); } } /* Make data unsigned if neccessary. */ outsampbytes = _af_format_sample_size_uncompressed(&out, AF_FALSE); if (out.sampleFormat == AF_SAMPFMT_UNSIGNED) addmod(¤t, _AFnewmodinst(signed2unsigned[outsampbytes])); /* Handle nasty 3-byte output cases. */ if (isinteger(outfc) && outsampbytes == 3) { if (!reading || out.compressionType != AF_COMPRESSION_NONE) { /* We're writing 3-byte ints into a file. We have 4-byte ints. Squish them to 3 by truncating the high byte off. we could also be reading/writing ints into a compressor. note this works for signed and unsigned, and has to. */ addmod(¤t, _AFnewmodinst(&char3_to_real_char3)); } else /* reading, not compressed */ { /* We're reading 3-byte ints into the user's buffer. The user expects 1. 4-byte sign-extended ints (3 bytes sign extended in 4 bytes) or 2. 4-byte unsigned ints (3 bytes in 4 bytes). How convenient: this is just what we have. */ } } if (out.byteOrder != _AF_BYTEORDER_NATIVE) { int bytes_per_samp = _af_format_sample_size_uncompressed(&out, reading); if (bytes_per_samp > 1 && out.compressionType == AF_COMPRESSION_NONE) { assert(swapbytes[bytes_per_samp]); addmod(¤t, _AFnewmodinst(swapbytes[bytes_per_samp])); } } /* Add file writer, possibly a compressor. */ if (!reading) if (AF_FAIL == addfilewritemods(¤t, track, h)) return(AF_FAIL); /* Now all modules are arranged! */ track->ms.nmodules = current.modinst - track->ms.module; #ifdef UNLIMITED_CHUNK_NVFRAMES /* OPTIMIZATION: normally, when we set up the modules, AFreadframes and AFwriteframes must pull and push chunks of size at most _AF_ATOMIC_NVFRAMES. For the simplest configurations of modules (1 module, no compression), no buffering at all needs to be done by the module system. In these cases, afReadFrames/afWriteFrames can pull/push as many virtual frames as they want in one call. This flag tells tells afReadFrames and afWriteFrames whether they can do so. Note that if this flag is set, file modules cannot rely on the intermediate working buffer which _AFsetupmodules usually allocates for them in their input or output chunk (for reading or writing, respectively). This is why if we are reading/writing compressed data, this optimization is turned off. There are warnings to this effect in the pcm (uncompressed) file read/write module. If you want to apply this optimization to other types, be sure to put similar warnings in the code. */ if (track->ms.nmodules == 1 && track->v.compressionType == AF_COMPRESSION_NONE && track->f.compressionType == AF_COMPRESSION_NONE) track->ms.mustuseatomicnvframes = AF_FALSE; else track->ms.mustuseatomicnvframes = AF_TRUE; #else track->ms.mustuseatomicnvframes = AF_TRUE; #endif return AF_SUCCEED; } /* disposemodules will free old buffers and free old modules, except those marked with free_on_close. The modules existing before we dispose them could be: 1. none (we may have only called _AFinitmodules and not _AFsetupmodules) 2. some invalid PARTIALLY ALLOCATED ones (e.g. the last _AFsetupmodules had an error) or 3. a perfectly valid set of modules. disposemodules will deal with all three cases. */ static void disposemodules (_Track *track) { if (track->ms.module) { int i; for (i=0; i < MAX_MODULES; i++) { _AFmoduleinst *mod = &track->ms.module[i]; #ifdef AF_DEBUG if (!mod->valid && i < track->ms.nmodules) printf("disposemodules: WARNING in-range invalid module found '%s'\n", mod->mod->name); #endif if (mod->valid && !mod->free_on_close && mod->mod->free) { (*mod->mod->free)(mod); mod->valid = AF_FALSE; } } free(track->ms.module); track->ms.module = AF_NULL; } track->ms.nmodules = 0; if (track->ms.chunk) { free(track->ms.chunk); track->ms.chunk = AF_NULL; } if (track->ms.buffer) { int i; for (i=0; i < (MAX_MODULES+1); i++) { if (track->ms.buffer[i] != AF_NULL) { free(track->ms.buffer[i]); track->ms.buffer[i] = AF_NULL; } } free(track->ms.buffer); track->ms.buffer = AF_NULL; } } /* resetmodules: see advanced section in README.modules for more info */ static status resetmodules (_AFfilehandle *h, _Track *track) { int i; /* We should already have called _AFsetupmodules. (Actually this is called from the end of _AFsetupmodules but whatever). */ assert(!track->ms.modulesdirty); /* Assume all is well with track. */ track->filemodhappy = AF_TRUE; CHNK(printf("resetmodules running reset1 routines\n")); /* Reset all modules. */ for (i=track->ms.nmodules-1; i >= 0; i--) { /* reset1 */ if (track->ms.module[i].mod->reset1 != AF_NULL) (*track->ms.module[i].mod->reset1)(&track->ms.module[i]); } /* Clear out frames2ignore here; the modules will increment it. */ track->frames2ignore = 0; if (!track->filemodhappy) return AF_FAIL; CHNK(printf("resetmodules running reset2 routines\n")); for (i=0; i < track->ms.nmodules; i++) { /* reset2 */ if (track->ms.module[i].mod->reset2 != AF_NULL) (*track->ms.module[i].mod->reset2)(&track->ms.module[i]); } CHNK(printf("resetmodules completed\n")); if (!track->filemodhappy) return AF_FAIL; #ifdef ONE_TRACK_ONLY /* For an explanation of this, see the comment in initfilemods which explains how and when the file is lseek'ed. */ if (h->seekok) if (lseek(h->fd, track->fpos_next_frame, SEEK_SET) < 0) { _af_error(AF_BAD_LSEEK, "unable to position read pointer at next data frame"); return AF_FAIL; } #endif return AF_SUCCEED; } /* _AFsyncmodules */ status _AFsyncmodules (AFfilehandle h, _Track *track) { int i; /* We should already have called _AFsetupmodules. */ assert(!track->ms.modulesdirty); /* Assume all is well with track. */ track->filemodhappy = AF_TRUE; CHNK(printf("_AFsyncmodules running sync1 routines\n")); /* Sync all modules. */ for(i=track->ms.nmodules-1; i >= 0; i-- ) { /* sync1 */ if (AF_NULL != track->ms.module[i].mod->sync1) (*track->ms.module[i].mod->sync1)(&track->ms.module[i]); } if (!track->filemodhappy) return AF_FAIL; CHNK(printf("_AFsyncmodules running sync2 routines\n")); for (i=0; i < track->ms.nmodules; i++) { /* sync2 */ if (AF_NULL != track->ms.module[i].mod->sync2) (*track->ms.module[i].mod->sync2)(&track->ms.module[i]); } CHNK(printf("_AFsyncmodules completed\n")); if (!track->filemodhappy) return AF_FAIL; #ifdef ONE_TRACK_ONLY /* For an explanation of this, see the comment in initfilemods which explains how and when the file is lseek'ed. */ if (h->seekok) if (lseek( h->fd, track->fpos_next_frame, SEEK_SET) < 0 ) { _af_error(AF_BAD_LSEEK, "unable to position write ptr at next data frame"); return(AF_FAIL); } #endif return AF_SUCCEED; } /* _AFsetupmodules: - frees any old modules, chunks, and buffers - looks at the input and output format and sets up a whole new set of input and output modules (using arrangemodules()) - assigns those modules chunks - allocates buffers and assigns the buffers to the chunks - initializes various track fields pertaining to the module system It returns AF_FAIL on any kind of error. It sets modulesdirty to AF_FALSE if it was able to clean the modules (although an error still could have occurred after cleaning them). */ status _AFsetupmodules (AFfilehandle h, _Track *track) { _AFmoduleinst *modules; _AFchunk *chunks; void **buffers; int maxbufsize, bufsize, i; double rateratiof2v, fframepos; /* The purpose of this function is to "clean" the modules: * All of the fields in trk->ms are completely set and valid. * track->totalvframes and track->next[fv]frame are set and valid and trk->modulesdirty will be set to AF_FALSE if this function succeeds. This function also resets the modules on files open for read. it will return AF_FAIL if either cleaning the modules fails, or this reset fails. The comments will tell you which part does what. */ /* NOTE: we cannot trust any value in track->ms until we have called disposemodules(), at which time things are cleared to reasonable "zero" values. It is possible for track->ms to be in an illegal state at this point, if the last _AFsetupmodules failed with an error. We can trust track->totalvframes and track->next[fv]frame because they are only modified after successfully building the modules. */ /* Disallow compression in virtual format for now. */ if (track->v.compressionType != AF_COMPRESSION_NONE) { _af_error(AF_BAD_NOT_IMPLEMENTED, "library does not support compression in virtual format yet"); return AF_FAIL; } /* Check that virtual compression parameters are ok. */ { int idx = _af_compression_index_from_id(track->v.compressionType); if ((*_af_compression[idx].fmtok)(&track->v) == AF_FALSE) { return AF_FAIL; } } /* track->nextvframe and track->nextfframe: At this point, only track->nextvframe contains useful information, since track->nextfframe may be swayed by currently buffered frames. Also track->nextvframe is currently in the scale of the old sampling rate, not the new one we are setting up. So at this point we remember where we are in the file (in floating point) in terms of the file sampling rate. We will use this later in this function to set both track->nextfframe (for reading) and track->nextvframe (for reading and writing). We must be careful to use the old rates, not the ones in track->{f,v}. */ /* If modules have been set up at all */ if (track->ms.old_v_rate > 0) { assert(track->ms.old_f_rate > 0); rateratiof2v = track->ms.old_f_rate / track->ms.old_v_rate; fframepos = track->nextvframe * rateratiof2v; } else /* We start at frame zero. */ fframepos = 0; /* Dispose the existing modules (except extended-life ones). See the function for info on what the module state could be at this time. */ disposemodules(track); /* Here we allocate the highest number of module instances (and chunks) chained together we could possibly need. This is how the chunks are used: module[n]'s input chunk is chunk[n] module[n]'s output chunk is chunk[n+1] chunk[n]'s buffer, if it is not the user's buffer, is buffer[n]. For reading chunk[0] is not usually used. For writing chunk[nmodules] is not usually used. We allocate a buffer for chunk[0] on reading and chunk[nmodules] when writing because the file reading or file writing module, if it does compression or decompression, may need extra space in which to place the result of its processing before reading or writing it. Also note that chunk[0].f and chunk[nmodules].f are used in arrangemodules(). */ modules = _af_malloc(sizeof (_AFmoduleinst) * MAX_MODULES); if (modules == AF_NULL) return AF_FAIL; for (i=0; i < MAX_MODULES; i++) modules[i].valid = AF_FALSE; chunks = _af_malloc(sizeof (_AFchunk) * (MAX_MODULES+1)); if (chunks == AF_NULL) return AF_FAIL; buffers = _af_malloc(sizeof (void *) * (MAX_MODULES+1)); if (buffers == AF_NULL) return AF_FAIL; /* It is very important to initialize each buffers[i] to NULL; dispose frees them all if !NULL. */ for (i=0; i < (MAX_MODULES+1); i++) buffers[i] = AF_NULL; track->ms.module = modules; /* nmodules is a bogus value here, set just for sanity (in case of broken code). */ track->ms.nmodules = 0; track->ms.chunk = chunks; track->ms.buffer = buffers; /* Figure out the best modules to use to convert the data and initialize instances of those modules. Fills "track->ms.module" and most of "track->ms.chunk" arrays (all but the buffers) as it goes. Sets "track->ms.nmodules" As a side benefit, this function also leaves information about the data format at each stage in the "f" field of each chunk. */ if (arrangemodules(h, track) == AF_FAIL) { /* At this point the modules are in an incompletely initialized and probably illegal state. nmodules could be meaningful or not. Things are nasty. But as long as any API call that uses the modules calls _AFsetupmodules() first (which then calls disposemodules(), which can handle this nastiness), we can restore the modules to a sane initial state and things will be ok. */ return AF_FAIL; } /* At this point modules and nmodules are almost completely filled in (modules aren't actually connected to one another), but buffer[n] and chunk[n].buf are still in a null state. track->totalvframes and track->next[fv]frame have not yet been set to a valid state. */ /* Now go through the modules: 1. Connect up the source/sink fields properly. 2. Use the information left in the _AudioFormat field of each chunk by setupmodules() along with the "max_pull"/"max_push" module function to figure out the biggest buffer size that could be needed. */ /* filemod reports error here */ track->filemodhappy = AF_TRUE; maxbufsize = 0; if (h->access == _AF_READ_ACCESS) { track->ms.chunk[track->ms.nmodules].nframes = _AF_ATOMIC_NVFRAMES; for (i=track->ms.nmodules-1; i >= 0; i--) { _AFchunk *inc = &track->ms.chunk[i]; _AFchunk *outc = &track->ms.chunk[i+1]; /* check bufsize needed for current output chunk */ bufsize = outc->nframes * _af_format_frame_size(&outc->f, AF_TRUE); if (bufsize > maxbufsize) maxbufsize = bufsize; if (i != 0) { /* Connect source pointer for this module. */ track->ms.module[i].u.pull.source = &track->ms.module[i-1]; } /* Determine inc->nframes from outc->nframes. If the max_pull function is present, we use it, otherwise we assume module does no weird buffering or rate conversion. */ if (track->ms.module[i].mod->max_pull) (*track->ms.module[i].mod->max_pull)(&track->ms.module[i]); else inc->nframes = outc->nframes; } if (!track->filemodhappy) return AF_FAIL; /* Check bufsize needed for filemod's input chunk (intermediate buffer) based on an uncompressed (output chunk) framesize. */ { _AFmoduleinst *filemod = &track->ms.module[0]; bufsize = filemod->inc->nframes * _af_format_frame_size(&filemod->outc->f, AF_TRUE); if (bufsize > maxbufsize) maxbufsize = bufsize; } } else { track->ms.chunk[0].nframes = _AF_ATOMIC_NVFRAMES; for (i=0; i < track->ms.nmodules; i++) { _AFchunk *inc = &track->ms.chunk[i]; _AFchunk *outc = &track->ms.chunk[i+1]; /* Check bufsize needed for current input chunk. */ bufsize = inc->nframes * _af_format_frame_size(&inc->f, AF_TRUE); if (bufsize > maxbufsize) maxbufsize = bufsize; if (i != track->ms.nmodules-1) { /* Connect sink pointer. */ track->ms.module[i].u.push.sink = &track->ms.module[i+1]; } /* Determine outc->nframes from inc->nframes. If the max_push function is present, we use it, otherwise we assume module does no weird buffering or rate conversion. */ if (track->ms.module[i].mod->max_push) (*track->ms.module[i].mod->max_push)(&track->ms.module[i]); else outc->nframes = inc->nframes; } if (!track->filemodhappy) return AF_FAIL; /* Check bufsize needed for filemod's output chunk (intermediate buffer) based on an uncompressed (input chunk) framesize. */ { _AFmoduleinst *filemod = &track->ms.module[track->ms.nmodules-1]; bufsize = filemod->outc->nframes * _af_format_frame_size(&filemod->inc->f, AF_TRUE); if (bufsize > maxbufsize) maxbufsize = bufsize; } } /* At this point everything is totally set up with the modules except that the chunk buffers have not been allocated, and thus buffer[n] and chunk[n].buf have not been set. But now we know how big they should be (maxbufsize). track->totalvframes and track->next[fv]frame have not yet been set to a valid state. */ DEBG(printf("_AFsetupmodules: maxbufsize=%d\n", maxbufsize)); /* One of these will get overwritten to point to user's buffer. The other one will be allocated below (for file read/write module). */ track->ms.chunk[track->ms.nmodules].buf = AF_NULL; track->ms.chunk[0].buf = AF_NULL; /* One of these will be allocated for the file read/write module The other will be completely unused. */ track->ms.buffer[track->ms.nmodules] = AF_NULL; track->ms.buffer[0] = AF_NULL; /* Now that we know how big buffers have to be, allocate buffers and assign them to the module instances. Note that track->ms.chunk[nmodules].buf (reading) or track->ms.chunk[0].buf (writing) will get overwritten in _AFreadframes or _AFwriteframes to point to the user's buffer. We allocate a buffer for track->ms.chunk[0].buf (reading) or track->ms.chunk[nmodules].buf (writing) not because it is needed for the modules to work, but as a working buffer for the file reading / file writing modules. Also note that some modules may change their inc->buf or outc->buf to point to something internal to the module before calling their source or sink. So module code must be careful not to assume that a buffer address will not change. Only for chunk[nmodules] (reading) or chunk[0] (writing) is such trickery disallowed. */ if (h->access == _AF_READ_ACCESS) for (i=track->ms.nmodules-1; i >= 0; i--) { if ((track->ms.buffer[i] = _af_malloc(maxbufsize)) == AF_NULL) return AF_FAIL; track->ms.chunk[i].buf = track->ms.buffer[i]; } else for (i=1; i <= track->ms.nmodules; i++) { if ((track->ms.buffer[i] = _af_malloc(maxbufsize)) == AF_NULL) return AF_FAIL; track->ms.chunk[i].buf = track->ms.buffer[i]; } /* Hooray! The modules are now in a completely valid state. But we can't set track->ms.modulesdirty to AF_FALSE yet... track->totalvframes and track->next[fv]frame have not yet been set to a valid state. */ if (h->access == _AF_READ_ACCESS) { /* Set total number of virtual frames based on new rate. */ if (track->totalfframes == -1) track->totalvframes = -1; else track->totalvframes = track->totalfframes * (track->v.sampleRate / track->f.sampleRate); /* track->nextvframe and track->nextfframe: Currently our only indication of where we were in the file is the variable fframepos, which contains (in floating point) our offset in file frames based on the old track->nextvframe. Now we get as close as we can to that original position, given the new sampling rate. */ track->nextfframe = (AFframecount) fframepos; track->nextvframe = (AFframecount) (fframepos * (track->v.sampleRate / track->f.sampleRate)); /* Now we can say the module system is in a clean state. Any errors we get from here on are reported but not critical. */ track->ms.modulesdirty = AF_FALSE; /* Set up for next time. */ track->ms.old_f_rate = track->f.sampleRate; track->ms.old_v_rate = track->v.sampleRate; /* Now we reset all the modules. If we are here because the user did afSeekFrame, the actual seek will be performed here. Otherwise this reset will set things up so that we are at the same file offset we were at before (or as close as possible given a change in rate conversion). */ /* Report error, but we're still clean. */ if (AF_SUCCEED != resetmodules(h, track)) return AF_FAIL; } /* Handle the case of _AF_WRITE_ACCESS. */ else { /* Don't mess with track->nextfframe or track->totalfframes. Scale virtual frame position relative to old virtual position. */ track->nextvframe = track->totalvframes = (AFframecount) (fframepos * (track->v.sampleRate / track->f.sampleRate)); /* Now we can say the module system is in a clean state. Any errors we get from here on are reported but not critical. */ track->ms.modulesdirty = AF_FALSE; /* Set up for next time. */ track->ms.old_f_rate = track->f.sampleRate; track->ms.old_v_rate = track->v.sampleRate; } DEBG(_af_print_filehandle(h)); #ifdef DEBUG for (i=track->ms.nmodules-1; i >= 0; i--) { _AFmoduleinst *inst = &track->ms.module[i]; } { /* Print format summary. */ printf("%s ->\n", (h->access == _AF_READ_ACCESS) ? "file" : "user"); for (i=0; i < track->ms.nmodules; i++) { _AFmoduleinst *inst = &track->ms.module[i]; _af_print_audioformat(&inst->inc->f); printf(" -> %s(%d) ->\n", inst->mod->name, i); } _af_print_audioformat(&track->ms.chunk[track->ms.nmodules].f); printf(" -> %s\n", (h->access != _AF_READ_ACCESS) ? "file" : "user"); } #endif /* If we get here, then not only are the modules clean, but whatever we did after the modules became clean succeeded. So we gloat about our success. */ return AF_SUCCEED; } /* _AFinitmodules: this routine sets the initial value of the module- related fields of the track when the track is first created. It also initializes the file read or file write modules. See README.modules for info on this. Set "modulesdirty" flag on each track, so that the first read/write/seek will set up the modules. */ status _AFinitmodules (AFfilehandle h, _Track *track) { track->channelMatrix = NULL; /* HACK: see private.h for a description of this hack */ track->taper = 10; track->dynamic_range = 100; track->ratecvt_filter_params_set = AF_TRUE; track->ms.nmodules = 0; track->ms.module = NULL; track->ms.chunk = NULL; track->ms.buffer = NULL; track->ms.modulesdirty = AF_TRUE; track->ms.filemodinst.valid = AF_FALSE; track->ms.filemod_rebufferinst.valid = AF_FALSE; track->ms.rateconvertinst.valid = AF_FALSE; track->ms.rateconvert_rebufferinst.valid = AF_FALSE; /* bogus value in case of bad code */ track->ms.mustuseatomicnvframes = AF_TRUE; /* old_f_rate and old_v_rate MUST be set to <= 0 here. */ track->ms.old_f_rate = -1; track->ms.old_v_rate = -1; /* Initialize extended-life file read or file write modules. */ if (AF_FAIL == initfilemods(track, h)) return AF_FAIL; /* Initialize extended-life rate convert modules (to NULL). */ initrateconvertmods(h->access == _AF_READ_ACCESS, track); /* NOTE: Only now that we have initialized filemods is track->totalfframes guaranteed to be ready. (The unit cannot always tell how many frames are in the file.) */ /* totalfframes could be -1. */ track->totalvframes = track->totalfframes; track->nextvframe = 0; track->frames2ignore = 0; return AF_SUCCEED; } /* _AFfreemodules: called once when filehandle is being freed opposite of initmodules free all modules, even the active ones */ void _AFfreemodules (_Track *track) { disposemodules(track); disposefilemods(track); #if 0 /* XXXmpruett rate conversion is deactivated for now */ disposerateconvertmods(track); #endif }