/* offlinemix-core.c - mixing audio files with offsets * * Copyright (C) 2007 by Gianni Ciolli * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA * ********************************************************************/ /* this is version 1.0rc1, finished 20070321. Please send comments and bugs to . */ /*** TODO ************************************************************ clean up unused variables *********************************************************************/ /******************************************************************** ** Chronology: ** 20070314 - version 0.1 ** First version, without sndfile buffering. ** 20070314 - version 0.2 ** Added input sndfile buffering. ** 20070314 - version 0.3 ** Added output sndfile buffering. ** Note that version 0.3 is 10 times faster than 0.2 ** and 20 times faster than 0.1 :-) ** 20070314 - version 0.4 ** Added options --start (-s), --end (-e), --duration (-d) ** to render only a small portion of the file ** 20070321 - version 1.0 ** First usable version. Here we list the main features. ** ** - Input files can be either mono or stereo, while the ** output file is always stereo. ** ** - We have four possible tags: offset, gain, vol, ** pb. The latter two generate events (i.e. they have a ** temporal coordinate), while the former two specify ** atemporal properties. ** ** - Can specify either 16 bit, 24 bit or 32 bit output. ** ********************************************************************/ #include #include #include #include #include #include #include /*** Constants ***/ #define AUDIOBUFFRAMES 44100 #define OPTION_START 0x1 #define OPTION_END 0x2 #define OPTION_DURATION 0x4 #define MAX_PARAM 3 char *TAGS[]={ "offset","vol", "pb", "gain"}; #define N_TAGS 4 #define TAG_OFFSET 0 #define TAG_VOL 1 #define TAG_PB 2 #define TAG_GAIN 3 #define EVENT_NOTIME -1e10 /*** types ***/ typedef struct { int tag; int nparm; int t; int channel; double v[MAX_PARAM]; } event; typedef struct { double gain; double vt; double v0; double v1; double pbt; double pb0; double pb1; } state; typedef struct { double a0; double a1; } bidouble; //typedef struct { // int l; // int r; //} chinf; /*** Global variables ***/ int SAMPLE_RATE,FRAMESIZE; int **ABuf,*ABufWindow,*FromInt,*ToInt,*EventN; #define OUTPUT_CHANNELS 2 double *From,*To,*Gain; //chinf *Chinf; struct SF_INFO *Sfi; SNDFILE **Sf; event *Events; /*** Procedures ***/ #define Err(format, ...) { fprintf (stderr, "[ERROR]\t" format ". Exiting.\n", ##__VA_ARGS__); exit(-1); } #define Warn(format, ...) fprintf (stderr, "[WARN]\t" format ".\n", ##__VA_ARGS__) #define Log(format, ...) { fprintf (stderr, "[LOG]\t" format ".\n", ##__VA_ARGS__); fflush(stderr); } #define LogN(format, ...) fprintf (stderr, "\n[LOG]\t" format ".\n", ##__VA_ARGS__) #define Filename(i) true_argv[2+(i)*2] #define Parameter(i) #define Alloca(p,t,c) { p=(t *)malloc(sizeof(t)*c); if(p==NULL) Err("Cannot malloc.");} int state_reset(state *s) { s->gain=1.0; s->vt=0.0; s->v0=1.0; s->v1=0.0; s->pbt=0.0; s->pb0=0.5; s->pb1=0.0; return 0; } int state_view(int i,state *s) { Log("Channel %d : vol = %f + %f t , pb = %f + %f t",i,s->v0,s->v1,s->pb0,s->pb1); return 0; } int* get_frame(int f,int n) { // returns pointer to frame n in file f; audio data is buffered in // windows. int l; if (nToInt[f]) return NULL; else { int n0=n-FromInt[f]; int i=n0/AUDIOBUFFRAMES; int j=n0%AUDIOBUFFRAMES; if (i!=ABufWindow[f]) { ABufWindow[f]=i; sf_seek(Sf[f],i*AUDIOBUFFRAMES,SEEK_SET); sf_readf_int(Sf[f],ABuf[f],AUDIOBUFFRAMES); } return ABuf[f]+j*Sfi[f].channels; } } int event_view(event *e) { // logs an event int i; fprintf(stderr,"Event %d at frame %d in channel %d with params",(*e).tag,(*e).t,(*e).channel); fprintf(stderr," %f",(*e).v[0]); for(i=1;i<(*e).nparm;i++) fprintf(stderr,",%f",(*e).v[i]); fprintf(stderr,"\n"); return 0; } int event_cmp(const void *a0,const void *b0) { // this is for sorting the event list const event *a=(event *)a0; const event *b=(event *)b0; return ( (*a).t - (*b).t ); } int parameter_tag(char *p) { // p must point to a parameter, i.e. a string delimited either by // null or by ':', whose syntax is TAG=V0,V1,...,Vk. This procedure // returns the integer corresponding to TAG, or -1 if no TAG // corresponds. int i; for(i=0;i %s",k,TAGS[k]); if( (k!=TAG_OFFSET) && (k!=TAG_GAIN) ) nevents++; j=skip_until_char(p,j,':'); } } return nevents; } int get_events(char **argv,int nfiles,int nevents,event *ep) { int i,j,j0,k,l,t; // k = Event index int ie,iv,ip,dj; double x; char *p,*q; // TAG=V0,V1,V2:TAG=V0,V1,V2:...:TAG=V0,V1,V2 //LogN("[*] Getting events."); for(ip=ie=0;ip \"%s\"",p+j); t=parameter_tag(p+j); if(t==-1) Err("unknown tag at \"%s\"",p+j); j=1+skip_until_char(p,j,'='); switch(t) { case TAG_OFFSET: case TAG_GAIN: x=strtod(p+j,&q); if(q==p+j) Err("cannot read double at \"%s\"",p+j); if(t==TAG_OFFSET) From[ip]=x; else Gain[ip]=x; j=skip_until_char(p,j,':'); break; default: //Log("Event tag %d -> %s in channel %d",t,TAGS[t],ip); // getting event ep[ie].channel=ip; ep[ie].tag=t; // reading values k=skip_until_char(p,j,':'); iv=-1; for(j0=j;j \"%s\"",p+j0); x=strtod(p+j0,&q); dj=q-p-j0; if(!dj) Err("cannot read double at \"%s\"",p+j0); //Log("dj=%d",dj); if(iv==-1) ep[ie].t=(int)(x*SAMPLE_RATE); else ep[ie].v[iv]=x; iv++; //Log("now p+j -> \"%s\"",p+j); j0=j+1; } // last value x=strtod(p+j0,&q); if(p+j0==q) Err("cannot read double at \"%s\"",p+j0); ep[ie].v[iv]=x; ep[ie].nparm=iv+1; //Log("Event %d:",ie); //event_view(ep+ie); //Log("X p+j0 -> \"%s\"",p+j0); //Log("X p+j -> \"%s\"",p+j); //LogN("Read %d values",iv); ie++; j=k; } } } return ie; } process_event(event *e,state *s0) { state *s=s0+e->channel; //Log("Processing event %s on channel %d at time %d",TAGS[e->tag],e->channel,e->t); switch(e->tag) { case TAG_VOL: s->v0 = e->v[0] - e->t * e->v[1] / SAMPLE_RATE; s->v1 = e->v[1] / SAMPLE_RATE; break; case TAG_PB: s->pb0 = e->v[0] - e->t * e->v[1] / SAMPLE_RATE; s->pb1 = e->v[1] / SAMPLE_RATE; break; } } /*** Main procedure ***/ int main(int argc, char **argv) { int i,i1,i2,j,k=0,l,*p,*q,*ABufOut,mini,maxi,lastFrame,*EventI; //int *n; int nevents,nfiles; int ei; double maxTo,minFrom,optS,optE,optD; double mu,pb; int flag=0; int c,true_argc; char** true_argv; // for getopt.h int OUTPUT_FORMAT_TYPE=SF_FORMAT_WAV; int OUTPUT_FORMAT_SUB=SF_FORMAT_PCM_16; state* State; /*** (1) processing options ***/ while (1) { static struct option long_options[] = { {"start", required_argument, 0, 's'}, {"end", required_argument, 0, 'e'}, {"duration", required_argument, 0, 'd'}, {"pcm32", no_argument, 0, '4'}, {"pcm24", no_argument, 0, '3'}, {0, 0, 0, 0} }; int option_index = 0; c = getopt_long (argc, argv, "34s:e:d:", long_options, &option_index); if (c == -1) break; switch (c) { case '4': OUTPUT_FORMAT_SUB=SF_FORMAT_PCM_32; Log("chosen 32 bit output"); break; case '3': OUTPUT_FORMAT_SUB=SF_FORMAT_PCM_24; Log("chosen 24 bit output"); break; case 's': flag=flag|OPTION_START; optS=atof(optarg); Log ("set start to %f ", optS); break; case 'e': flag=flag|OPTION_END; optE=atof(optarg); Log ("set end to %f ", optE); break; case 'd': flag=flag|OPTION_DURATION; optD=atof(optarg); Log ("set duration to %f ", optD); break; default: abort (); } } if(flag==(OPTION_START|OPTION_END|OPTION_DURATION)) Err("Cannot set start, end and duration together!"); true_argc=argc-optind+1; true_argv=argv+optind-1; //Log("Argc=%d,Argv=%s,%s...",true_argc,true_argv[0],true_argv[1]); //Log("Argc%%2=%d,Argc<4=%d...",true_argc%2,true_argc<4); if((true_argc<4)||(true_argc%2)) { printf("\n" "Usage: offlinemix-core [OPTION] ... OUTFILE FILE1 PARAM1 [FILE2 PARAM2 [FILE3 PARAM3 ... ]]\n" "Mixes FILE1 FILE2 ... into OUTFILE according to PARAM1 PARAM2 ...\n" "FILEs must be WAV files, all at the same sample rate;\n" "each PARAM is a string containing a sequence of tags separated by ':'\n" "each tag has the form TAG=VALUES\n" "TAG is one of: vol pb offset gain\n" "VALUES is a sequence of floating-point numbers separated by ','\n" "\nTAG=(gain|offset)\n\trequires 1 value\n" "\nTAG=(vol|pb)\n\trequires 2 or 3 values: t0,a0,a1 (default a1=0)\n" "\tvolume (or pan/balance) at time t is given by a0+(t-t0)*a1\n" "\nOptions:" "\n\n-s, --start=T\n\t" "start rendering from instant T" "\n\n-e, --end=T\n\t" "stop rendering at the instant T" "\n\n-d, --duration=T\n\t" "render exactly T seconds" "\n\n-3, --pcm24\n\t" "output will have 24 bit PCM samples" "\n\n-4, --pcm32\n\t" "output will have 32 bit PCM samples" "\n"); return -1; } nfiles=(true_argc-1)/2; printf("I will mix %d files.\n",nfiles); /*** (2) Allocating memory, phase 1 ***/ // Sf=(SNDFILE **)malloc(sizeof(SNDFILE *)*(nfiles+1)); if(Sf==NULL) Err("Cannot malloc Sf."); Alloca(Sf,SNDFILE*,nfiles+1); Alloca(Sfi,struct SF_INFO,nfiles+1); //Alloca(Chinf,chinf,nfiles); Alloca(State,state,nfiles); /*** (3) Checking formats, setting rate and channels ***/ k=0; for(i=0;iFrom[i]) minFrom=From[i]; } //Log("File %s: %d frames, %f-%f",Filename(i),(int)(Sfi[i].frames),From[i],To[i]); } /*** (7) Preparing output file ***/ //LogN("[*] Preparing output file."); Sfi[nfiles].format=OUTPUT_FORMAT_TYPE|OUTPUT_FORMAT_SUB; Sfi[nfiles].samplerate=Sfi[0].samplerate; Sf[nfiles]=sf_open(true_argv[1],SFM_WRITE,Sfi+nfiles); if(Sf[nfiles]==NULL) Err("Cannot open output file %s",true_argv[1]); /*** (8) Determining exact rendering window ***/ //LogN("[*] Determining exact rendering window."); if(flag&OPTION_START) mini=(int)floor(optS*SAMPLE_RATE); else mini=(int)floor(minFrom*SAMPLE_RATE); if(flag&OPTION_END) maxi=(int)ceil(optE*SAMPLE_RATE); else maxi=(int)ceil(maxTo*SAMPLE_RATE); if(flag&OPTION_DURATION) if(flag&OPTION_START) maxi=(int)ceil((optS+optD)*SAMPLE_RATE); else mini=(int)floor((optE-optD)*SAMPLE_RATE); //Log("Rendering window: from %f to %f (frames %d to %d)", // ((double)mini)/SAMPLE_RATE,((double)maxi)/SAMPLE_RATE, // mini,maxi); ABufOut=ABuf[nfiles]; // recall that the output file is the last one /*** (8bis) Pre-rendering loop ***/ //LogN("[*] Pre-rendering loop"); for(i=0;i %d",j); } lastFrame=0; ABufWindow[nfiles]=i1; } p=ABufOut+i2*OUTPUT_CHANNELS; // pointer to current frame in buffer // (9.2) reset p[0],...,p[OUTPUT_CHANNELS] for (l=0;llastFrame) lastFrame=i2; } printf(" done.\n"); /*** () flushing remaining write buffer ***/ //LogN("[*] Flushing remaining write buffer"); sf_seek(Sf[nfiles],ABufWindow[nfiles]*AUDIOBUFFRAMES,SEEK_SET); sf_writef_int(Sf[nfiles],ABufOut,lastFrame+1); sf_close(Sf[nfiles]); //LogN("[*] End of rendering"); return 0; } // format is described here. // typedef struct // { sf_count_t frames ; /* Used to be called samples. */ // int samplerate ; // int channels ; // int format ; // int sections ; // int seekable ; // } SF_INFO ; // useful audio format constants: // SF_FORMAT_WAV = 0x010000, /* Microsoft WAV format (little endian). */ // SF_FORMAT_PCM_16 = 0x0002, /* Signed 16 bit data */ // SF_FORMAT_PCM_32 = 0x0004, /* Signed 32 bit data */ // SF_FORMAT_SUBMASK = 0x0000FFFF, // SF_FORMAT_TYPEMASK = 0x0FFF0000,