/* * Copyright (c) 1983-2013 Richard Dobson and Composers Desktop Project Ltd * http://people.bath.ac.uk/masrwd * http://www.composersdesktop.com * This file is part of the CDP System. * The CDP System is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The CDP System 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 Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with the CDP System; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ //tdelay.cpp //mono/stereo tapped delay line //RWD,CDP 1998 //VERSION: initial, 0.01 //usage: tdelay [-f] infile outfile feedback tapfile.txt [trailertime] // -f : floatsam output // feedback (double) - obvious... // tapfile.txt lines of at least two values: time amplitude [pan] // times must be increasing, non-identical // amplitude +- 1.0 // pan -1.0--1.0: -1 = hard Left, 1= hard Right, 0.0 = Centre #include #include #include #include extern "C" { #include } #include #include "reverberator.h" #include #include using namespace std; typedef struct stereo_tap { double time; double amp; double pan; } STAP ; void usage(void); #define ROOT2 (1.4142136) enum pan_direction {SIGNAL_TO_LEFT,SIGNAL_TO_RIGHT}; enum args {INFILE=1,OUTFILE,TAPGAIN,FEEDBACK,MIX,TAPFILE,TRTIME}; void pancalc(double position,double *leftgain,double *rightgain); const char* cdp_version = "5.0.0"; int main(int argc, char **argv) { int ifd,ofd,inchans; float feedback = 0.0f; float dryfac = 0.0f,wetfac = 1.0f; double trailertime = 0.0; double current_time = 0.0; deltap *l_taps = 0,*r_taps = 0; tapdelay *l_dline = 0, *r_dline = 0; unsigned int i,nchans = 1,ntaps = 0,trailersamps = 0; bool is_stereo = false; bool floatsams_wanted = false; bool mix_input = true; SFPROPS props; FILE *tapfile = 0; double sample_duration,tapgain; double max_lgain = 1.0,max_rgain= 1.0; float max_gain = 1.0f; double sr; CHPEAK *peaks; if((argc==2) && strcmp(argv[1],"--version")==0) { fprintf(stdout,"%s\n",cdp_version); fflush(stdout); return 0; } if(argc < TRTIME){ usage(); exit(1); } if(sflinit("tdelay")){ sfperror("tdelay: initialisation"); exit(1); } while(argv[1][0] == '-'){ switch(argv[1][1]){ //case('s'): // is_stereo = true; // break; case('f'): floatsams_wanted = true; break; default: fprintf(stderr,"\nillegal flag option %s",argv[1]); usage(); exit(1); break; } argv++; argc--; } if(argc < TRTIME){ usage(); exit(1); } if((ifd = sndopenEx(argv[INFILE],0,CDP_OPEN_RDONLY)) < 0) { fprintf(stderr,"reverb: cannot open input file %s: %s\n", argv[1],rsferrstr); exit(1); } if(!snd_headread(ifd,&props)){ fprintf(stderr,"\nerror reading infile header: %s\n",rsferrstr); sndcloseEx(ifd); exit(1); } if(props.type != wt_wave){ fprintf(stderr,"\ninfile is not a waveform file"); sndcloseEx(ifd); exit(1); } inchans = props.chans; if(inchans > 2){ fprintf(stderr,"\nSorry - cannot process files with more than two channels!"); sndcloseEx(ifd); exit(1); } sample_duration = 1.0 / props.srate; if(floatsams_wanted) props.samptype = FLOAT32; tapgain = atof(argv[TAPGAIN]); if(tapgain <= 0.0){ fprintf(stderr,"\nError: tapgain must be > 0.0\n"); exit(1); } feedback = (float) atof(argv[FEEDBACK]); if(feedback < -1.0f || feedback > 1.0f){ fprintf(stderr,"\nfeedback out of range -1.0 to +1.0"); usage(); sndcloseEx(ifd); exit(1); } dryfac = (float) atof(argv[MIX]); if(dryfac < 0.0f || dryfac >= 1.0f){ fprintf(stderr,"\nmix value %s out of range",argv[4]); usage(); sndcloseEx(ifd); exit(1); } wetfac = 1.0f - dryfac; if(argc==TRTIME+1){ trailertime = atof(argv[TRTIME]); if(trailertime < 0.0){ fprintf(stderr,"\ntrailertime must be >= 0.0"); exit(1); } trailersamps = (unsigned int) (trailertime * props.srate); } tapfile = fopen(argv[TAPFILE],"r"); if(!tapfile){ fprintf(stderr,"\nunable to open tapfile %s",argv[TAPFILE]); usage(); sndcloseEx(ifd); exit(1); } //read all lines, into taps array vector ltaps; STAP thistap; double this_time,this_amp,this_pan; char line[255]; int linecount = 1; this_time = 0.0; this_amp = 0.0; bool read_error = false; vector::iterator I; //RWD TODO: take a tap at time = 0.0 as spec of direct sound : amp and pan while(fgets(line,255,tapfile) != NULL){ this_pan = 0.0; int got; if(line[0] == '\n' || line[0]== ';'){ //allow comment lines; this does not check for leading white-space... linecount++; continue; } if((got = sscanf(line,"%lf%lf%lf",&this_time,&this_amp,&this_pan)) < 2){ fprintf(stderr,"\nerror in tapfile: line %d: insufficient parameters",linecount); read_error = true; break; } //time= 0 no good for a taptime! if(this_time < 0.0){ fprintf(stderr,"\nerror in tapfile: line %d: bad time value",linecount); read_error = true; break; } if(this_time < sample_duration) { //non-zero time must be at least one sample on! fprintf(stderr,"\nWARNING: very small taptime %.4lf treated as zero - ignoring mix value",this_time); this_time = 0.0; mix_input = false; } if(current_time != 0.0 && this_time==current_time){ fprintf(stderr,"\nWARNING: duplicate time in line %d: ignored",linecount); linecount++; continue; } if(this_time < current_time){ fprintf(stderr,"\nerror in tapfile: time out of sequence: line %d",linecount); read_error = true; break; } current_time = this_time; thistap.time = this_time; if(this_amp < 0.0 || this_amp > 1.0){ fprintf(stderr,"\nerror in tapfile: line %d: bad amplitude value",linecount); read_error = true; break; } thistap.amp = this_amp; if(got==3){ thistap.pan = this_pan; is_stereo = true; nchans = 2; } else thistap.pan = 0.0; ltaps.push_back(thistap); linecount++; } if(read_error){ sndcloseEx(ifd); exit(1); } ntaps = ltaps.size(); if(ntaps==0){ printf("\nfile contains no data!"); sndcloseEx(ifd); exit(1); } if(!mix_input) printf("\nzero taptime used: taking input mix control from tapfile"); #ifdef _DEBUG printf("\nread %d taps",ntaps); for(I = ltaps.begin(); I != ltaps.end();I++) printf("\n%.4lf\t%.4lf\t%.4lf",I->time,I->amp,I->pan); //or whatever..... printf("\nfeedback = %.4f",feedback); printf("\ndryfac = %.4f, wetfac = %.4f",dryfac,wetfac); #endif if(is_stereo) printf("\ncreating stereo outfile"); else printf("\ncreating mono outfile"); l_taps = new deltap[ntaps]; if(is_stereo){ r_taps = new deltap[ntaps]; //and create stereo delay-lines int j; for(j = 0,I = ltaps.begin(); I != ltaps.end();j++,I++){ //printf("\n%.4lf\t%.4lf\t%.4lf",I->time,I->amp,I->pan); //or whatever..... double l_gain,r_gain; pancalc(I->pan,&l_gain,&r_gain); l_taps[j].pos = r_taps[j].pos = I->time; l_taps[j].val = I->amp * l_gain; r_taps[j].val = I->amp * r_gain; } } //else create a mono delayline else { int j; for(j = 0,I = ltaps.begin(); I != ltaps.end();j++,I++){ l_taps[j].pos = I->time; l_taps[j].val = I->amp; } } l_dline = new tapdelay(l_taps,ntaps,props.srate); if(!l_dline->create()){ fputs("\nunable to create delay line - probably no memory",stderr); delete [] l_taps; sndcloseEx(ifd); exit(1); } max_lgain = l_dline->get_maxgain(); if(is_stereo){ r_dline = new tapdelay(r_taps,ntaps,props.srate); if(!r_dline->create()){ fputs("\nunable to create stereo delay line - probably no memory",stderr); delete [] l_taps; delete [] r_taps; delete l_dline; sndcloseEx(ifd); exit(1); } max_rgain = r_dline->get_maxgain(); } if(is_stereo) max_gain = (float) min(max_lgain,max_rgain); else max_gain = (float) max_lgain; /* recale max_gain by user param, eg:*/ max_gain *= (float) tapgain; l_dline->set_gain((double)max_gain); if(is_stereo) r_dline->set_gain((double)max_gain); props.chans = nchans; peaks = (CHPEAK *) malloc(sizeof(CHPEAK) * nchans); if(peaks==NULL){ fputs("No memory for PEAK data",stderr); exit(1); } for(i=0; i < nchans; i++){ peaks[i].value = 0.0f; peaks[i].position = 0; } //OK, now open outfile! ofd = sndcreat_ex(argv[OUTFILE], -1,&props,SFILE_CDP,CDP_CREATE_NORMAL); //ofd = sndcreat_formatted(argv[OUTFILE],-1,outsamp_type,nchans,props.srate,CDP_CREATE_NORMAL); if(ofd < 0){ printf("\nunable to open outfile %s",argv[OUTFILE]); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } printf("\n"); float l_op = 0.0f; float r_op = 0.0f; float l_out = 0.0f; float r_out = 0.0f; long outsamps = 0; long step = (long) (0.25 * props.srate); //RWD.10.98 BIG TODO: optimize all this! sr = (double) props.srate; if(is_stereo){ for(;;){ int rv; float ip,l_ip,r_ip; if((rv = fgetfloatEx(&l_ip,ifd,0)) < 0){ fprintf(stderr,"\nerror reading infile data"); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } if(!rv) break; ip = l_ip; if(inchans==2){ //mix stereo ip to mono for delay purposers if((rv = fgetfloatEx(&r_ip,ifd,0)) < 0){ fprintf(stderr,"\nerror reading infile data"); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } if(!rv) break; ip = (l_ip + r_ip) * 0.5f; } if(feedback != 0.0f) { l_op = l_dline->tick(ip + (l_op * feedback)); r_op = r_dline->tick(ip + (r_op * feedback)); } else{ l_op = l_dline->tick(ip); r_op = r_dline->tick(ip); } //mix mono or stereo input with panned output: if(mix_input){ l_out = l_op * wetfac + l_ip * dryfac; r_out = r_op * wetfac; if(inchans==2) r_out += r_ip * dryfac; else r_out += l_ip * dryfac; } else{ l_out = l_op; r_out = r_op; } /* rescale */ l_out *= max_gain; r_out *= max_gain; if((float)fabs((double)l_out) > peaks[0].value) { peaks[0].value = (float)fabs((double)l_out); peaks[0].position = outsamps; } if((float)fabs((double)r_out) > peaks[1].value) { peaks[1].value = (float)fabs((double)r_out); peaks[1].position = outsamps; } if(fputfloatEx(&l_out,ofd) < 1){ fprintf(stderr,"\nerror writing to outfile: %s",sferrstr()); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } if(fputfloatEx(&r_out,ofd) < 1){ fprintf(stderr,"\nerror writing to outfile: %s",sferrstr()); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } outsamps++; if((outsamps % step) == 0) //inform(step,props.srate); fprintf(stdout,"%.2f\r",(double)outsamps/sr); } for(i = 0; i < trailersamps;i++){ if(feedback != 0.0f){ l_op = l_dline->tick(feedback * l_op); r_op = r_dline->tick(feedback * r_op); } else { l_op = l_dline->tick(0.0f); r_op = r_dline->tick(0.0f); } if(mix_input){ l_out = l_op * wetfac; r_out = r_op * wetfac; } else { l_out = l_op; r_out = r_op; } /* rescale */ l_out *= max_gain; r_out *= max_gain; if((float)fabs((double)l_out) > peaks[0].value) { peaks[0].value = (float)fabs((double)l_out); peaks[0].position = outsamps; } if((float)fabs((double)r_out) > peaks[1].value) { peaks[1].value = (float)fabs((double)r_out); peaks[1].position = outsamps; } if(fputfloatEx(&l_out,ofd) < 1){ fprintf(stderr,"\nerror writing to outfile: %s",sferrstr()); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } if(fputfloatEx(&r_out,ofd) < 1){ fprintf(stderr,"\nerror writing to outfile: %s",sferrstr()); delete [] l_taps; delete l_dline; if(r_taps) delete [] r_taps; sndcloseEx(ifd); exit(1); } outsamps++; if((outsamps % step) == 0) //inform(step,props.srate); fprintf(stdout,"%.2f\r",(double)outsamps/sr); } } else{ for(;;){ int rv; float ip,l_ip,r_ip; if((rv = fgetfloatEx(&l_ip,ifd,0)) < 0){ fprintf(stderr,"\nerror reading infile data"); delete [] l_taps; delete l_dline; sndcloseEx(ifd); exit(1); } if(!rv) break; if(inchans==2){ //mix stereo ip to mono for delay purposers if((rv = fgetfloatEx(&r_ip,ifd,0)) < 0){ fprintf(stderr,"\nerror reading infile data"); delete [] l_taps; delete l_dline; sndcloseEx(ifd); exit(1); } if(!rv) break; ip = (l_ip + r_ip) * 0.5f; } else ip = l_ip; if(feedback != 0.0f) l_op = l_dline->tick(ip + l_op * feedback); else l_op = l_dline->tick(ip); if(mix_input) l_out = l_op * wetfac + l_ip * dryfac; else l_out = l_op; /* rescale */ l_out *= max_gain; if((float)fabs((double)l_out) > peaks[0].value) { peaks[0].value = (float)fabs((double)l_out); peaks[0].position = outsamps; } if(fputfloatEx(&l_out,ofd) < 1){ fprintf(stderr,"\nerror writing to outfile"); delete [] l_taps; delete l_dline; sndcloseEx(ifd); exit(1); } outsamps++; if((outsamps % step) == 0) //inform(step,props.srate); fprintf(stdout,"%.2f\r",(double)outsamps/sr); } for(i = 0; i < trailersamps;i++){ l_op = l_dline->tick(feedback * l_op); if(mix_input) l_out = l_op * wetfac; else l_out = l_op; if((float)fabs((double)l_out) > peaks[0].value) { peaks[0].value = (float)fabs((double)l_out); peaks[0].position = outsamps; } if(fputfloatEx(&l_out,ofd) < 1){ fprintf(stderr,"\nerror writing to outfile"); delete [] l_taps; delete l_dline; sndcloseEx(ifd); exit(1); } outsamps++; if((outsamps % step) == 0) //inform(step,props.srate); fprintf(stdout,"%.2f\r",(double)outsamps/sr); } } fprintf(stdout,"%.2f\n",(double)outsamps/sr); printf("\nPEAK data:\n"); for(i=0;i < nchans;i++) printf("Channel %u: %.4f at frame %d: %.4lf secs\n",i+1, peaks[i].value,peaks[i].position, (double) peaks[i].position / (double)props.srate); sndputpeaks(ofd,nchans,peaks); if(sndcloseEx(ofd) < 0){ fprintf(stderr,"\nwarning: error closing outfile"); } delete [] l_taps; delete l_dline; if(is_stereo) { delete [] r_taps; delete r_dline; } sndcloseEx(ifd); return 0; } void usage(void) { printf("\n******* STEREO MULTI-TAPPED DELAY WITH PANNING v1.0 1998 : CDP Release 4 ******\n"); printf("\nusage:\n\ttapdelay [-f] infile outfile tapgain feedback mix taps.txt [trailtime]\n"); printf("\n\t-f\t\twrite floating-point outfile"); printf("\n\t\t\t(default: outfile has same format as infile)"); printf("\n\ttapgain\t\tgain factor applied to output from delayline"); printf("\n\t\t\t(tapgain > 0.0, typ 0.25)"); printf("\n\tfeedback\trange: -1 <= feeedback <= 1.0"); printf("\n\tmix\t\tproportion of source mixed with delay output.\n\t\t\trange: 0.0 <= mix < 1.0"); printf("\n\ttrailtime\textra time in seconds (beyond infile dur) "); printf("\n\t\t\t for delays to play out."); printf("\n\tStereo inputs are mixed to mono at input to the delay line.\n"); printf("\n\ttaps.txt consists of list of taps in the format:"); printf("\n\ttime amp [pan]"); printf("\n\n\tAll values are floating-point, one tap per line."); printf("\n\tTimes (seconds) must be increasing. Duplicate times are ignored."); printf("\n\tA zero time (no delay) overrides the mix parameter,"); printf("\n\t and determines the level and pan of the (mono) input."); printf("\n\tAmp values must be in the range 0.0 to 1.0"); printf("\n\tEmpty lines are permitted, as are lines starting with a semni-colon,"); printf("\n\t which may be used for comments."); printf("\n\tIf a pan value is used in any line, outfile will be stereo."); printf("\n\tPan values are nominally in the range -1 to +1: 0 = centre (mono)"); printf("\n\t within which constant-power panning is used."); printf("\n\tValues beyond these limits result in attenuation according to the"); printf("\n\t inverse-square law, to suggest distance beyond the speakers.\n"); } //TW's constant-power pan routine, even does inv-square reductions beyond the speakers void pancalc(double position,double *leftgain,double *rightgain) { int dirflag; double temp; double relpos; double reldist, invsquare; if(position < 0.0) dirflag = SIGNAL_TO_LEFT; /* signal on left */ else dirflag = SIGNAL_TO_RIGHT; if(position < 0) relpos = -position; else relpos = position; if(relpos <= 1.0){ /* between the speakers */ temp = 1.0 + (relpos * relpos); reldist = ROOT2 / sqrt(temp); temp = (position + 1.0) / 2.0; *rightgain = temp * reldist; *leftgain = (1.0 - temp ) * reldist; } else { /* outside the speakers */ temp = (relpos * relpos) + 1.0; /*RWD: NB same in both cases! */ reldist = sqrt(temp) / ROOT2; /* relative distance to source */ invsquare = 1.0 / (reldist * reldist); if(dirflag == SIGNAL_TO_LEFT){ *leftgain = invsquare; *rightgain = 0.0; } else { /* SIGNAL_TO_RIGHT */ *rightgain = invsquare; *leftgain = 0; } } }