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