1 /*
2     cvanal.c:
3 
4     Copyright (C) 1996 Greg Sullivan, John ffitch
5 
6     This file is part of Csound.
7 
8     The Csound Library is free software; you can redistribute it
9     and/or modify it under the terms of the GNU Lesser General Public
10     License as published by the Free Software Foundation; either
11     version 2.1 of the License, or (at your option) any later version.
12 
13     Csound is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU Lesser General Public License for more details.
17 
18     You should have received a copy of the GNU Lesser General Public
19     License along with Csound; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21     02110-1301 USA
22 */
23 
24 /************************************************************************/
25 /*                                                                      */
26 /*  Performs a FFT on a time domain soundfile, and saves the result in  */
27 /*  a file.                                                             */
28 /*  Purpose is to create a frequency domain version of an impulse       */
29 /*  response, for the later use by the convolve operator.               */
30 /*  Greg Sullivan                                                       */
31 /************************************************************************/
32 
33 #include "std_util.h"
34 #include "soundio.h"
35 #include "convolve.h"
36 
37 static int32_t takeFFT(CSOUND *csound, SOUNDIN *inputSound, CVSTRUCT *outputCVH,
38                        int64_t Hlenpadded, SNDFILE *infd, FILE *ofd, int32_t nf);
39 static int32_t quit(CSOUND*, char *msg);
40 static int32_t CVAlloc(CSOUND*, CVSTRUCT**, int64_t, int32_t, MYFLT,
41                        int32_t, int32_t, int64_t, int32_t, int32_t);
42 
43 #define SF_UNK_LEN      -1      /* code for sndfile len unkown  */
44 
45 #define FIND(MSG)   if (*s == '\0')  \
46                       if (UNLIKELY(!(--argc) || ((s = *++argv) && *s == '-')))    \
47                         return quit(csound,MSG);
48 
cvanal(CSOUND * csound,int32_t argc,char ** argv)49 static int32_t cvanal(CSOUND *csound, int32_t argc, char **argv)
50 {
51     CVSTRUCT *cvh;
52     char    *infilnam, *outfilnam;
53     SNDFILE *infd;
54     FILE    *ofd;
55     void    *ofd_handle;
56     int32_t err, channel = ALLCHNLS;
57     SOUNDIN *p;  /* space allocated by SAsndgetset() */
58     MYFLT   beg_time = FL(0.0), input_dur = FL(0.0), sr = FL(0.0);
59     int64_t Estdatasiz, Hlen;
60     int64_t Hlenpadded = 1;
61     char    err_msg[512];
62     int32_t res;
63     int32_t new_format = 0;
64 
65     /* csound->dbfs_to_float = csound->e0dbfs = FL(1.0); */
66     if (UNLIKELY(!(--argc))) {
67       return quit(csound,Str("insufficient arguments"));
68     }
69     do {
70       char *s = *++argv;
71       if (*s++ == '-')
72         switch (*s++) {
73         case 's':
74           FIND(Str("no sampling rate"))
75 #ifdef USE_DOUBLE
76           csound->sscanf(s, "%lf", &sr);
77 #else
78           csound->sscanf(s, "%f", &sr);
79 #endif
80           break;
81         case 'c':
82           FIND(Str("no channel"))
83             sscanf(s, "%d", &channel);
84           if (UNLIKELY((channel < 1) || (channel > 4)))
85             return quit(csound, Str("channel must be in the range 1 to 4"));
86           break;
87         case 'b':
88           FIND(Str("no begin time"))
89 #ifdef USE_DOUBLE
90           csound->sscanf(s, "%lf", &beg_time);
91 #else
92           csound->sscanf(s, "%f", &beg_time);
93 #endif
94           break;
95         case 'd':
96           FIND(Str("no duration time"))
97 #ifdef USE_DOUBLE
98           csound->sscanf(s, "%lf", &input_dur);
99 #else
100           csound->sscanf(s, "%f", &input_dur);
101 #endif
102           break;
103         case 'X':
104           new_format = 1;
105           break;
106         default:   return quit(csound, Str("unrecognised switch option"));
107         }
108       else break;
109     } while (--argc);
110 
111     if (UNLIKELY(argc !=  2))
112       return quit(csound, Str("illegal number of filenames"));
113     infilnam = *argv++;
114     outfilnam = *argv;
115 
116     if (UNLIKELY((infd = csound->SAsndgetset(csound, infilnam, &p, &beg_time,
117                                              &input_dur, &sr, channel)) == NULL)) {
118       snprintf(err_msg, 512, Str("error while opening %s"), infilnam);
119       return quit(csound, err_msg);
120     }
121     sr = (MYFLT) p->sr;
122 
123     Hlen = p->getframes;
124     while (Hlenpadded < 2*Hlen-1)
125       Hlenpadded <<= 1;
126 
127     Estdatasiz = (Hlenpadded + 2) * sizeof(MYFLT);
128     if (channel == ALLCHNLS)
129       Estdatasiz *= p->nchanls;
130 
131     /* alloc & fill CV hdrblk */
132     if (UNLIKELY((err = CVAlloc(csound, &cvh, Estdatasiz, CVMYFLT, sr,
133                                 p->nchanls, channel, Hlen, CVRECT, 4)))) {
134       csound->Message(csound, "%s", Str("cvanal: Error allocating header\n"));
135       return -1;
136     }
137     if (new_format) {
138 
139       ofd_handle = csound->FileOpen2(csound, &ofd, CSFILE_STD, outfilnam, "w",
140                                      "SFDIR", CSFTYPE_CVANAL, 0);
141       if (UNLIKELY(ofd_handle == NULL)) {         /* open the output CV file */
142         return quit(csound, Str("cannot create output file"));
143       }                                          /* & wrt hdr into the file */
144 #if defined(USE_DOUBLE)
145       fprintf(ofd, "CVANAL\n%d %d %d %.17lg %d %d %d %d\n",
146               cvh->headBsize,              /* total number of bytes of data */
147               cvh->dataBsize,              /* total number of bytes of data */
148               cvh->dataFormat,             /* (int32_t) format specifier */
149               (double)cvh->samplingRate,   /* of original sample */
150               cvh->src_chnls,              /* no. of channels in source */
151               cvh->channel,                /* requested channel(s) */
152               cvh->Hlen,                   /* length of impulse reponse */
153               cvh->Format);                /* (int32_t) how words are org'd in frm */
154 #else
155       fprintf(ofd, "CVANAL\n%d %d %d %.9g %d %d %d %d\n",
156               cvh->headBsize,              /* total number of bytes of data */
157               cvh->dataBsize,              /* total number of bytes of data */
158               cvh->dataFormat,             /* (int32_t) format specifier */
159               (double)cvh->samplingRate,   /* of original sample */
160               cvh->src_chnls,              /* no. of channels in source */
161               cvh->channel,                /* requested channel(s) */
162               cvh->Hlen,                   /* length of impulse reponse */
163               cvh->Format);                /* (int32_t) how words are org'd in frm */
164 #endif
165     }
166     else {
167       ofd_handle = csound->FileOpen2(csound, &ofd, CSFILE_STD, outfilnam, "wb",
168                                      "SFDIR", CSFTYPE_CVANAL, 0);
169       if (UNLIKELY(ofd_handle == NULL)) {           /* open the output CV file */
170         return quit(csound, Str("cannot create output file"));
171       }                                           /* & wrt hdr into the file */
172       if (UNLIKELY((int64_t) fwrite(cvh, 1, cvh->headBsize, ofd) < cvh->headBsize)) {
173         return quit(csound, Str("cannot write header"));
174       }
175     }
176     res = takeFFT(csound, p, cvh, Hlenpadded, infd, ofd, new_format);
177     csound->Message(csound, "%s", Str("cvanal finished\n"));
178     return (res != 0 ? -1 : 0);
179 }
180 
quit(CSOUND * csound,char * msg)181 static int32_t quit(CSOUND *csound, char *msg)
182 {
183     csound->Message(csound, Str("cvanal error: %s\n"), msg);
184     csound->Message(csound, "%s", Str("Usage: cvanal [-d<duration>] "
185                             "[-c<channel>] [-b<begin time>] [-X] <input soundfile>"
186                             " <output impulse response FFT file>\n"));
187     return -1;
188 }
189 
takeFFT(CSOUND * csound,SOUNDIN * p,CVSTRUCT * cvh,int64_t Hlenpadded,SNDFILE * infd,FILE * ofd,int32_t nf)190 static int32_t takeFFT(CSOUND *csound, SOUNDIN *p, CVSTRUCT *cvh,
191                    int64_t Hlenpadded, SNDFILE *infd, FILE *ofd, int32_t nf)
192 {
193     int32_t i, j, read_in;
194     MYFLT   *inbuf, *outbuf;
195     MYFLT   *fp1, *fp2;
196     int32_t Hlen = (int32_t) cvh->Hlen;
197     int32_t nchanls;
198 
199     nchanls = cvh->channel != ALLCHNLS ? 1 : cvh->src_chnls;
200     j = (int32_t) (Hlen * nchanls);
201     inbuf = fp1 = (MYFLT *) csound->Malloc(csound, j * sizeof(MYFLT));
202     if (UNLIKELY((read_in = csound->getsndin(csound, infd, inbuf, j, p)) < j)) {
203       csound->Message(csound, "%s", Str("less sound than expected!\n"));
204       return -1;
205     }
206     /* normalize the samples read in. */
207     for (i = read_in; i--; ) {
208       *fp1++ *= 1.0/csound->Get0dBFS(csound);
209     }
210 
211     fp1 = inbuf;
212     outbuf = fp2 = (MYFLT*) csound->Malloc(csound,
213                                            sizeof(MYFLT) * (Hlenpadded + 2));
214     /* for (i = 0; i < (Hlenpadded + 2); i++) */
215     /*   outbuf[i] = FL(0.0); */
216     memset(outbuf, 0, sizeof(MYFLT)*(Hlenpadded + 2));
217 
218     for (i = 0; i < nchanls; i++) {
219       for (j = Hlen; j > 0; j--) {
220         *fp2++ = *fp1;
221         fp1 += nchanls;
222       }
223       fp1 = inbuf + i + 1;
224       csound->RealFFT(csound, outbuf, (int32_t) Hlenpadded);
225       outbuf[Hlenpadded] = outbuf[1];
226       outbuf[1] = outbuf[Hlenpadded + 1L] = FL(0.0);
227       /* write straight out, just the indep vals */
228       if (nf) {
229         int32 i, l;
230         l = (cvh->dataBsize/nchanls)/sizeof(MYFLT);
231         for (i=0; i<l; i++) {
232             fprintf(ofd, "%a\n", (double)outbuf[i]);
233         }
234       }
235       else
236         if (UNLIKELY(1!=fwrite(outbuf, cvh->dataBsize/nchanls, 1, ofd)))
237           fprintf(stderr, "%s", Str("Write failure\n"));
238       for (j = Hlenpadded - Hlen; j > 0; j--)
239         fp2[j] = FL(0.0);
240       fp2 = outbuf;
241     }
242     return 0;
243 }
244 
CVAlloc(CSOUND * csound,CVSTRUCT ** pphdr,int64_t dataBsize,int32_t dataFormat,MYFLT srate,int32_t src_chnls,int32_t channel,int64_t Hlen,int32_t Format,int32_t infoBsize)245 static int32_t CVAlloc(
246     CSOUND      *csound,
247     CVSTRUCT    **pphdr,        /* returns address of new block */
248     int64_t     dataBsize,      /* desired bytesize of datablock */
249     int32_t     dataFormat,     /* data format - PVMYFLT etc */
250     MYFLT       srate,          /* sampling rate of original in Hz */
251     int32_t     src_chnls,      /* number of channels in source */
252     int32_t     channel,        /* requested channel(s) */
253     int64_t     Hlen,           /* impulse response length */
254     int32_t     Format,         /* format of frames: CVPOLAR, CVPVOC etc */
255     int32_t     infoBsize)      /* bytes to allocate in info region */
256 
257     /* Allocate memory for a new CVSTRUCT+data block;
258        fill in header according to passed in data.
259        Returns CVE_MALLOC  (& **pphdr = NULL) if malloc fails
260                CVE_OK      otherwise  */
261 {
262     int64_t  hSize;
263 
264     hSize = sizeof(CVSTRUCT) + infoBsize - CVDFLTBYTS;
265     if (( (*pphdr) = (CVSTRUCT *) csound->Malloc(csound, hSize)) == NULL )
266       return(CVE_MALLOC);
267     (*pphdr)->magic        = CVMAGIC;
268     (*pphdr)->headBsize    = hSize;
269     (*pphdr)->dataBsize    = dataBsize;
270     (*pphdr)->dataFormat   = dataFormat;
271     (*pphdr)->samplingRate = srate;
272     (*pphdr)->src_chnls    = src_chnls;
273     (*pphdr)->channel      = channel;
274     (*pphdr)->Hlen         = Hlen;
275     (*pphdr)->Format       = Format;
276     /* leave info bytes undefined */
277     return(CVE_OK);
278 }
279 
280 /* module interface */
281 
cvanal_init_(CSOUND * csound)282 int32_t cvanal_init_(CSOUND *csound)
283 {
284     int32_t retval = csound->AddUtility(csound, "cvanal", cvanal);
285     if (!retval) {
286       retval =
287         csound->SetUtilityDescription(csound, "cvanal",
288                                       Str("Soundfile analysis for convolve"));
289     }
290     return retval;
291 }
292 
293