/* * sfrecord: multichannel soundfile recorder (try [writesf~] instead) * * (c) 1999-2011 IOhannes m zmölnig, forum::für::umläute, institute of electronic music and acoustics (iem) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /* sfplay.c - Author: Winfried Ritsch - IEM Graz 10.Mai 99 - Modified: sfrecord.c - hacked from sfplay ::: 2308:forum::für::umläute:1999 @ iem please mail problems and ideas for improvements to ritsch@iem.kug.ac.at zmoelnig@iem.kug.ac.at */ /* TODO: deprecate this in favour of [writesf~] */ /* #define DEBUG_ME for debugging messages */ #include "zexy.h" /* #include "m_imp.h" */ #define DACBLKSIZE 64 /* in m_imp.h, but error if it is included it here*/ #include #include #include /* ------------------------ sfrecord ----------------------------- */ #define MAX_CHANS 8 /* channels for soundfiles 1,2,4,8 */ #ifdef __WIN32__ # define BINWRITEMODE "wb" #else # include # include # define BINWRITEMODE "w" #endif static t_class *sfrecord_class; typedef struct _sfrecord { t_object x_obj; void* filep; /* pointer to file data read in mem */ t_symbol* filename; /* filename */ /* because there is no command queue, flags are used instead */ t_int write; /* write: 1, stop: 0 */ t_int please_stop; /* can be reset only by stop-state itself */ t_int please_close; /* can be reset only by close-state */ t_int x_channels; /* channels to write */ t_float x_offset; /* offset to start writing */ t_float offset; /* inlet value offset in secs */ t_float x_skip; /* skip bytes because header */ t_int skip; /* pending skip if 1 */ t_float x_speed; /* write speed, not supported in this version */ t_int size; /* size of file (if memory mapped) */ t_int swap; /* swap bytes from l->b or b->m */ FILE *fp; /* file oper non-NULL of open */ t_int state; /* which state is writer in */ t_int count; /* count for ticks before next step */ } t_sfrecord; /* states of statemachine */ #define SFRECORD_WAIT 0 /* wait for open */ #define SFRECORD_OPEN 1 #define SFRECORD_CLOSE 2 #define SFRECORD_SKIP 3 #define SFRECORD_WRITE 4 #define SFRECORD_STOP 5 #define SFRECORD_ERROR -1 #define SFRECORD_WAITTICKS 10 /* 1 tick of 64 Samples is ca.1.5ms on 441000 */ /* split the os-calls in as many steps as possible to split them on different ticks in steps of SFRECORD_WAITTICKS to avoid peak performance */ /* like the one from garray */ static int sfrecord_am_i_big_endian(void) { unsigned short s = 1; unsigned char c = *(char *) (&s); #ifdef DEBUG_ME post("i am %s-endian", c?"little":"big"); #endif return(c==0); } static void state_out(t_sfrecord *x, int state) { /* outlet the actual state */ outlet_float(x->x_obj.ob_outlet, state); } /* METHOD: "open" file */ /* this dont use memory map, because I dont know about this on NT ? Use of the buffered functions fopen, fseek fread fclose instead the non buffered ones open read close */ static void sfrecord_open(t_sfrecord *x,t_symbol *filename, t_symbol *endian) { if(x->state != SFRECORD_WAIT) { post("sfrecord: first close %s before open %s",x->filename->s_name, filename->s_name); return; } /* test if big endian else asume little endian * should be 'l' but could be anything */ if(sfrecord_am_i_big_endian()) { x->swap = !(endian->s_name[0] == 'b'); } else { x->swap = (endian->s_name[0] == 'b'); } /* * skip header after open;; sometimes weŽll have to write a header using the x->skip; so donŽt delete it completely */ /* x->skip = 1; */ x->filename = filename; #ifdef DEBUG_ME post("sfrecord: opening %s",x->filename->s_name); #endif if (x->fp != NULL) { z_fclose(x->fp); /* should not happen */ } if (!(x->fp = z_fopen(x->filename->s_name,BINWRITEMODE))) { error("sfrecord: can't open %s", x->filename->s_name); } } /* METHOD: close */ static void sfrecord_close(t_sfrecord *x) { x->write = 0; x->please_close = 1; /* now in state machine if(x->fp != NULL) { z_fclose(x->fp); x->fp = NULL; } */ #ifdef DEBUG_ME post("sfrecord: closing "); #endif return; } /* for skipping header of soundfile DonŽt use this for memory map */ static int sfrecord_skip(t_sfrecord *x) { if(!x->skip) { return 0; } #ifdef DEBUG_ME post("sfrecord:skip to %f",x->x_skip); #endif x->skip = 0; return 1; } /* Input, method for Start stop */ static void sfrecord_start(t_sfrecord *x) { #ifdef DEBUG_ME post("sfrecord: start at %d", x->x_offset); #endif state_out(x, 1); x->write=1; } static void sfrecord_stop(t_sfrecord *x) { #ifdef DEBUG_ME post("sfrecord: stop"); #endif state_out(x, 0); x->write=0; x->please_stop = 1; } static void sfrecord_float(t_sfrecord *x, t_floatarg f) { int t = f; if (t) { sfrecord_start(x); } else { sfrecord_stop(x); } } /* say what state weŽre in */ static void sfrecord_bang(t_sfrecord* x) { if (x->state == SFRECORD_WRITE) { state_out(x, 1); } else { state_out(x, 0); } } /* ******************************************************************************** */ /* the work krow eht */ /* ******************************************************************************** */ static t_int *sfrecord_perform(t_int *w) { t_sfrecord* x = (t_sfrecord*)(w[1]); short* buf = x->filep; short* bufstart = buf; int c = x->x_channels; int i,j,n, s_n; t_float* in[MAX_CHANS]; short s; int swap = x->swap; for (i=0; istate) { /* just wait */ case SFRECORD_WAIT: if(x->fp != NULL) { #ifdef DEBUG_ME post("wait -> open"); #endif x->state = SFRECORD_OPEN; x->count = SFRECORD_WAITTICKS; }; break; /* if in open state, already opened but wait for skip */ case SFRECORD_OPEN: /* file has opened wait some time */ if(!(x->count--)) { #ifdef DEBUG_ME post("open -> skip"); #endif x->state = SFRECORD_SKIP; x->count = SFRECORD_WAITTICKS; }; break; /* in skipmode wait until ready for stop */ case SFRECORD_SKIP: if(x->count == SFRECORD_WAITTICKS) { if(!x->fp) { x->state = SFRECORD_CLOSE; x->count=1; #ifdef DEBUG_ME post("skip -> close"); #endif break; } sfrecord_skip(x); } if(!(x->count--)) { #ifdef DEBUG_ME post("skip -> stop"); #endif x->state = SFRECORD_STOP; x->count = SFRECORD_WAITTICKS; }; break; case SFRECORD_STOP: /* in stop state mainly waits for write */ x->please_stop = 0; if(x->please_close) { x->state = SFRECORD_CLOSE; x->count = SFRECORD_WAITTICKS; #ifdef DEBUG_ME post("stop -> close"); #endif } else if(x->skip) { x->state = SFRECORD_SKIP; x->count = SFRECORD_WAITTICKS; #ifdef DEBUG_ME post("stop -> skip"); #endif } else if(x->write) { #ifdef DEBUG_ME post("stop -> write"); #endif x->state = SFRECORD_WRITE; state_out(x, 1); } break; case SFRECORD_WRITE: /* yes write now */ if(!x->write || x->please_stop) { /* if closing dont need to go to stop */ if(x->please_close) { x->state = SFRECORD_CLOSE; x->count = SFRECORD_WAITTICKS; #ifdef DEBUG_ME post("write -> close"); #endif state_out(x, 0); } else { x->state = SFRECORD_STOP; #ifdef DEBUG_ME post("write -> stop"); #endif }; break; } /* should never happen */ if(!x->filep) { x->state = SFRECORD_ERROR; error("sfrecord: writing but no buffer ???? write"); return (w+4+c); } /* copy float to 16 Bit and swap if neccesairy */ /* LATER treat overflows */ while (n--) { for (i=0; i> 8); } *buf++ = s; } } /* then write soundfile 16 bit*/ if ( (j = fwrite(bufstart, sizeof(short), c*s_n, x->fp)) < 1) { x->state = SFRECORD_ERROR; x->count = SFRECORD_WAITTICKS; #ifdef DEBUG_ME post("write -> write error\t %xd\t%xd\t%d\t%d", x->filep, buf, c*s_n*sizeof(short), j); #endif break; } #if 0 if((j=fwrite(buf,sizeof(short),c*n,x->fp)) < (unsigned int) n) { if(feof(x->fp)) { while (n--) { for (i=0; i 0) { s = *buf++; if(swap) { s = ((s & 0xFF)<< 8) | ((s& 0xFF00) >> 8); } *out[i]++ = s*(1./32768.); } else { *out[i]++ = 0; } } } } x->state = SFRECORD_STOP; x->write = 0; return(w+c+3); } /* or error if(ferror()) */ x->state = SFRECORD_ERROR; x->count = SFRECORD_WAITTICKS; #ifdef DEBUG_ME post("write -> write error"); #endif break; }; #endif /* 0 */ return (w+c+3); /* writing was fine */ /* ok :?: write error, please close */ case SFRECORD_ERROR: if(!(x->count--)) { x->state = SFRECORD_CLOSE; sfrecord_close(x); #ifdef DEBUG_ME post("sfrecord error writing sf: error -> close"); #endif x->count = SFRECORD_WAITTICKS; } break; /* in close state go to wait afterwards */ case SFRECORD_CLOSE: x->please_close = 0; /* wait until ready for close operation */ if(!(x->count--)) { x->state = SFRECORD_WAIT; x->count = SFRECORD_WAITTICKS; /* avoid openfiles */ if(x->fp) { z_fclose(x->fp); x->fp = NULL; }; #ifdef DEBUG_ME post("sfrecord: close -> wait"); #endif } break; }; /*case */ return(w+c+3); } /* ---------------------- Setup junk -------------------------- */ static void sfrecord_dsp(t_sfrecord *x, t_signal **sp) { #ifdef DEBUG_ME post("sfrecord: dsp"); post("offset = %f\tspeed = %f\t", x->offset, x->x_speed); #endif switch (x->x_channels) { case 1: dsp_add(sfrecord_perform, 3, x, sp[0]->s_vec, /* in 1 */ sp[0]->s_n); break; case 2: dsp_add(sfrecord_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, sp[0]->s_n); break; case 4: dsp_add(sfrecord_perform, 6, x, sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec, sp[0]->s_n); break; case 8: dsp_add(sfrecord_perform, 9, x, sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec, sp[4]->s_vec, sp[5]->s_vec, sp[6]->s_vec, sp[7]->s_vec, sp[0]->s_n); break; } } /* create sfrecord with args */ static void *sfrecord_new(t_floatarg chan) { t_sfrecord *x = (t_sfrecord *)pd_new(sfrecord_class); t_int c = chan; switch(c) { /* ok */ case 1: case 2: case 4: case 8: break; /* try it, good luck ... */ case 3: c = 2; break; case 5: case 6: case 7: c=7; break; default: c=1; break; } outlet_new(&x->x_obj, gensym("float")); x->x_channels = c; x->x_skip = x->x_offset = 0; x->skip = 1; x->offset = 0.; x->x_speed = 1.0; x->write = 0; x->please_stop = 0; x->please_close = 0; x->state = SFRECORD_WAIT; x->count = 0; x->filename = NULL; x->fp = NULL; x->swap = 1; c--; while (c--) { #ifdef DEBUG_ME post("create extra channel #%d", c); #endif inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("signal"), gensym("signal")); /* channels inlet */ } x->filep = t_getbytes(DACBLKSIZE*sizeof(short)*x->x_channels); #ifdef DEBUG_ME post("get_bytes DACBLKSIZE*%d*%d->%ld",sizeof(short),x->x_channels, x->filep); post("sfrecord: x_channels = %d, x_speed = %f, x_skip = %f",x->x_channels, x->x_speed,x->x_skip); #endif return (x); } static void sfrecord_helper(void) { post("\nsfplay :: a raw-data soundfile-recorder"); post("\ncreation :: sfrecord \t: channels set the number of channels"); post("\nopen [] []\t:: open b(ig) or l(ittle) endian file" "\nclose\t\t\t:: close file (aka eject)" "\nstart\t\t\t:: start playing" "\nstop\t\t\t:: stop playing" "\nbang\t\t\t:: outputs the current state (1_recording, 0_not-recording)"); post("\n\nyou can also start recording with a '1', and stop with a '0'"); } static void sfrecord_free(t_sfrecord *x) { freebytes(x->filep, DACBLKSIZE*sizeof(short)*x->x_channels); } void sfrecord_setup(void) { sfrecord_class = class_new(gensym("sfrecord"), (t_newmethod)sfrecord_new, (t_method)sfrecord_free, sizeof(t_sfrecord), 0, A_DEFFLOAT, A_DEFFLOAT,0); class_addmethod(sfrecord_class, nullfn, gensym("signal"), 0); class_addmethod(sfrecord_class, (t_method)sfrecord_dsp, gensym("dsp"), 0); /* method open with filename */ class_addmethod(sfrecord_class, (t_method)sfrecord_open, gensym("open"), A_SYMBOL,A_SYMBOL,A_NULL); class_addmethod(sfrecord_class, (t_method)sfrecord_close, gensym("close"), A_NULL); class_addmethod(sfrecord_class, (t_method)sfrecord_start, gensym("start"), A_NULL); class_addmethod(sfrecord_class, (t_method)sfrecord_stop, gensym("stop"), A_NULL); /* start/stop with 0/1 */ class_addfloat(sfrecord_class, sfrecord_float); /* bang out the current-state to the outlet*/ class_addbang(sfrecord_class,sfrecord_bang); /* some help */ class_addmethod(sfrecord_class, (t_method)sfrecord_helper, gensym("help"), A_NULL); class_sethelpsymbol(sfrecord_class, gensym("sf-play_record")); zexy_register("sfrecord"); }