1 /*
2 pvfileio.c:
3
4 Copyright (C) 2000 Richard Dobson
5 (C) 2005 Istvan Varga
6
7 This file is part of Csound.
8
9 The Csound Library is free software; you can redistribute it
10 and/or modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
13
14 Csound is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with Csound; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 02110-1301 USA
23 */
24
25 /* pvfileio.c
26 * pvocex format test routines
27 *
28 * Initial version RWD May 2000.
29 * All rights reserved: work in progress!
30 *
31 * Manifestly not a complete API yet!
32 * In particular, error returns are kept very simplistic at the moment.
33 * (and are not even very consistent yet...)
34 * In time, a full set of error values and messages will be developed.
35 *
36 * NB: the RIFF<WAVE> functions only look for, and accept, a PVOCEX format file.
37 * NB also: if windows.h is included anywhere (should be no need in this file,
38 * or in pvfileio.h),
39 * the WAVE_FORMAT~ symbols may need to be #ifndef-ed.
40 */
41
42 /* very simple CUSTOM window chunk:
43 *
44 * <PVXW><size><data>
45 *
46 * where size as usual gives the size of the data in bytes.
47 * the size in samples much match dwWinlen (which may not be the same
48 * as N (fft length)
49 * the sample type must be the same as that of the pvoc data itself
50 * (only floatsams supported so far)
51 * values must be normalised to peak of 1.0
52 */
53
54 /* CSOUND NB: floats must be kept as 'float', not MYFLT,
55 as only 32bit floats supported at present.
56 */
57
58 #include "csoundCore.h"
59 #include "pvfileio.h"
60
61 #if !defined(WAVE_FORMAT_EXTENSIBLE)
62 #define WAVE_FORMAT_EXTENSIBLE (0xFFFE)
63 #endif
64 #ifndef WAVE_FORMAT_PCM
65 #define WAVE_FORMAT_PCM (0x0001)
66 #endif
67 #define WAVE_FORMAT_IEEE_FLOAT (0x0003)
68
69 #define PVFILETABLE ((PVOCFILE**) ((CSOUND*) csound)->pvFileTable)
70
71 const GUID KSDATAFORMAT_SUBTYPE_PVOC = {
72 0x8312b9c2, 0x2e6e, 0x11d4,
73 { 0xa8, 0x24, 0xde, 0x5b, 0x96, 0xc3, 0xab, 0x21 }
74 };
75
76 typedef struct pvoc_file {
77 WAVEFORMATEX fmtdata;
78 PVOCDATA pvdata;
79 int32_t datachunkoffset;
80 int32_t nFrames; /* no of frames in file */
81 int32_t FramePos; /* where we are in file */
82 FILE *fp;
83 void *fd;
84 int32_t curpos;
85 int32_t to_delete;
86 int32_t readonly;
87 char *name;
88 float *customWindow;
89 } PVOCFILE;
90
91 static const char *pvErrorStrings[] = {
92 Str_noop("\npvsys: (no error)"), /* 0 */
93 Str_noop("\npvsys: unknown error"), /* -1 */
94 Str_noop("\npvsys: already initialised"), /* -2 */
95 Str_noop("\npvsys: bad arguments"), /* -3 */
96 Str_noop("\npvsys: bad format parameter"), /* -4 */
97 Str_noop("\npvsys: bad window type"), /* -5 */
98 Str_noop("\npvsys: too many files open"), /* -6 */
99 Str_noop("\npvsys: unable to create file"), /* -7 */
100 Str_noop("\npvsys: Internal error: NULL data arrays"), /* -8 */
101 Str_noop("\npvsys: unable to open file"), /* -9 */
102 Str_noop("\npvsys: error reading Source format data"), /* -10 */
103 Str_noop("\npvsys: not a WAVE_EX file"), /* -11 */
104 Str_noop("\npvsys: bad size for fmt chunk"), /* -12 */
105 Str_noop("\npvsys: error reading Extended format data"), /* -13 */
106 Str_noop("\npvsys: not a PVOC-EX file"), /* -14 */
107 Str_noop("\npvsys: error reading Extended pvoc format data"), /* -15 */
108 Str_noop("\npvsys: unknown pvocex Version"), /* -16 */
109 Str_noop("\npvsys: error reading header"), /* -17 */
110 Str_noop("\npvsys: not a RIFF file"), /* -18 */
111 Str_noop("\npvsys: file too small"), /* -19 */
112 Str_noop("\npvsys: not a WAVE file"), /* -20 */
113 Str_noop("\npvsys: error reading format chunk"), /* -21 */
114 Str_noop("\npvsys: PVXW chunk found before fmt chunk."), /* -22 */
115 Str_noop("\npvsys: PVXW chunk found but custom window not specified"),/* -23 */
116 Str_noop("\npvsys: error reading window data."), /* -24 */
117 Str_noop("\npvsys: bad RIFF file"), /* -25 */
118 Str_noop("\npvsys: bad format, data chunk before fmt chunk"), /* -26 */
119 Str_noop("\npvsys: custom window chunk PVXW not found"), /* -27 */
120 Str_noop("\npvsys: error skipping unknown WAVE chunk"), /* -28 */
121 Str_noop("\npvsys: bad format in RIFF file"), /* -29 */
122 Str_noop("\npvsys: error writing header"), /* -30 */
123 Str_noop("\npvsys: error writing fmt chunk"), /* -31 */
124 Str_noop("\npvsys: error writing window data."), /* -32 */
125 Str_noop("\npvsys: error updating data chunk"), /* -33 */
126 Str_noop("\npvsys: error updating riff chunk"), /* -34 */
127 Str_noop("\npvsys: error seeking to end of file"), /* -35 */
128 Str_noop("\npvsys: file does not exist"), /* -36 */
129 Str_noop("\npvsys: file not open"), /* -37 */
130 Str_noop("\npvsys: bad file descriptor"), /* -38 */
131 Str_noop("\npvsys: error writing data"), /* -39 */
132 Str_noop("\npvsys: error reading data"), /* -40 */
133 Str_noop("\npvsys: error rewinding file"), /* -41 */
134 Str_noop("\npvsys: unable to close file on termination"), /* -42 */
135 NULL /* -43 */
136 };
137
138 static int32_t pvoc_writeheader(CSOUND *csound, PVOCFILE *p);
139 static int32_t pvoc_readheader(CSOUND *csound, PVOCFILE *p,
140 WAVEFORMATPVOCEX *pWfpx);
141
142 /* thanks to the SNDAN programmers for this! */
143 /* return 1 for big-endian machine, 0 for little-endian machine */
144
byte_order(void)145 static inline int32_t byte_order(void)
146 {
147 const int32_t one = 1;
148 return (!*((char*) &one));
149 }
150
151 /* low level file I/O */
152
pvfile_read_tag(PVOCFILE * p,char * s)153 static inline int32_t pvfile_read_tag(PVOCFILE *p, char *s)
154 {
155 if (UNLIKELY((int32_t) fread(s, 1, 4, p->fp) != 4)) {
156 s[0] = '\0';
157 return -1;
158 }
159 s[4] = '\0';
160 return 0;
161 }
162
pvfile_write_tag(PVOCFILE * p,const char * s)163 static inline int32_t pvfile_write_tag(PVOCFILE *p, const char *s)
164 {
165 if (UNLIKELY((int32_t) fwrite((void*) s, 1, 4, p->fp) != 4))
166 return -1;
167 return 0;
168 }
169
pvfile_read_16(PVOCFILE * p,void * data,int32_t cnt)170 static inline int32_t pvfile_read_16(PVOCFILE *p, void *data, int32_t cnt)
171 {
172 int32_t n = (int32_t) fread(data, sizeof(uint16_t), (size_t) cnt, p->fp);
173 if (byte_order()) {
174 int32_t i;
175 uint16_t tmp;
176 for (i = 0; i < n; i++) {
177 tmp = ((uint16_t*) data)[i];
178 tmp = ((tmp & (uint16_t) 0xFF) << 8) | ((tmp & (uint16_t) 0xFF00) >> 8);
179 ((uint16_t*) data)[i] = tmp;
180 }
181 }
182 return n;
183 }
184
pvfile_write_16(PVOCFILE * p,void * data,int32_t cnt)185 static inline int32_t pvfile_write_16(PVOCFILE *p, void *data, int32_t cnt)
186 {
187 int32_t n;
188
189 if (byte_order()) {
190 uint16_t tmp;
191 for (n = 0; n < cnt; n++) {
192 tmp = ((uint16_t*) data)[n];
193 tmp = ((tmp & (uint16_t) 0xFF) << 8) | ((tmp & (uint16_t) 0xFF00) >> 8);
194 if (fwrite(&tmp, sizeof(uint16_t), 1, p->fp) != (size_t) 1)
195 break;
196 }
197 }
198 else
199 n = fwrite(data, sizeof(uint16_t), (size_t) cnt, p->fp);
200 return (n != cnt);
201 }
202
pvfile_read_32(PVOCFILE * p,void * data,int32_t cnt)203 static inline int32_t pvfile_read_32(PVOCFILE *p, void *data, int32_t cnt)
204 {
205 int32_t n = (int32_t) fread(data, sizeof(uint32_t), (size_t) cnt, p->fp);
206 if (byte_order()) {
207 int32_t i;
208 uint32_t tmp;
209 for (i = 0; i < n; i++) {
210 tmp = ((uint32_t*) data)[i];
211 tmp = ((tmp & (uint32_t) 0x000000FFU) << 24)
212 | ((tmp & (uint32_t) 0x0000FF00U) << 8)
213 | ((tmp & (uint32_t) 0x00FF0000U) >> 8)
214 | ((tmp & (uint32_t) 0xFF000000U) >> 24);
215 ((uint32_t*) data)[i] = tmp;
216 }
217 }
218 return n;
219 }
220
pvfile_write_32(PVOCFILE * p,void * data,int32_t cnt)221 static inline int32_t pvfile_write_32(PVOCFILE *p, void *data, int32_t cnt)
222 {
223 int32_t n;
224
225 if (byte_order()) {
226 uint32_t tmp;
227 for (n = 0; n < cnt; n++) {
228 tmp = ((uint32_t*) data)[n];
229 tmp = ((tmp & (uint32_t) 0x000000FFU) << 24)
230 | ((tmp & (uint32_t) 0x0000FF00U) << 8)
231 | ((tmp & (uint32_t) 0x00FF0000U) >> 8)
232 | ((tmp & (uint32_t) 0xFF000000U) >> 24);
233 if (fwrite(&tmp, sizeof(uint32_t), 1, p->fp) != (size_t) 1)
234 break;
235 }
236 }
237 else
238 n = fwrite(data, sizeof(uint32_t), (size_t) cnt, p->fp);
239 return (n != cnt);
240 }
241
write_guid(PVOCFILE * p,const GUID * pGuid)242 static int32_t write_guid(PVOCFILE *p, const GUID *pGuid)
243 {
244 int32_t err = 0;
245 err |= pvfile_write_32(p, (void*) &(pGuid->Data1), 1L);
246 err |= pvfile_write_16(p, (void*) &(pGuid->Data2), 1L);
247 err |= pvfile_write_16(p, (void*) &(pGuid->Data3), 1L);
248 err |= ((int32_t) fwrite(&(pGuid->Data4[0]), 1, 8, p->fp) != 8);
249 return err;
250 }
251
compare_guids(const GUID * gleft,const GUID * gright)252 static int32_t compare_guids(const GUID *gleft, const GUID *gright)
253 {
254 const char *left = (const char *) gleft, *right = (const char *) gright;
255 return !memcmp(left, right, sizeof(GUID));
256 }
257
write_pvocdata(PVOCFILE * p)258 static int32_t write_pvocdata(PVOCFILE *p)
259 {
260 int32_t err = 0;
261 err |= pvfile_write_16(p, &(p->pvdata.wWordFormat), 1L);
262 err |= pvfile_write_16(p, &(p->pvdata.wAnalFormat), 1L);
263 err |= pvfile_write_16(p, &(p->pvdata.wSourceFormat), 1L);
264 err |= pvfile_write_16(p, &(p->pvdata.wWindowType), 1L);
265 err |= pvfile_write_32(p, &(p->pvdata.nAnalysisBins), 1L);
266 err |= pvfile_write_32(p, &(p->pvdata.dwWinlen), 1L);
267 err |= pvfile_write_32(p, &(p->pvdata.dwOverlap), 1L);
268 err |= pvfile_write_32(p, &(p->pvdata.dwFrameAlign), 1L);
269 err |= pvfile_write_32(p, &(p->pvdata.fAnalysisRate), 1L);
270 err |= pvfile_write_32(p, &(p->pvdata.fWindowParam), 1L);
271 return err;
272 }
273
write_fmt(PVOCFILE * p)274 static int32_t write_fmt(PVOCFILE *p)
275 {
276 int32_t err = 0;
277 err |= pvfile_write_16(p, &(p->fmtdata.wFormatTag), 1L);
278 err |= pvfile_write_16(p, &(p->fmtdata.nChannels), 1L);
279 err |= pvfile_write_32(p, &(p->fmtdata.nSamplesPerSec), 1L);
280 err |= pvfile_write_32(p, &(p->fmtdata.nAvgBytesPerSec), 1L);
281 err |= pvfile_write_16(p, &(p->fmtdata.nBlockAlign), 1L);
282 err |= pvfile_write_16(p, &(p->fmtdata.wBitsPerSample), 1L);
283 err |= pvfile_write_16(p, &(p->fmtdata.cbSize), 1L);
284 return err;
285 }
286
pvoc_writeWindow(PVOCFILE * p,float * window,uint32_t length)287 static int32_t pvoc_writeWindow(PVOCFILE *p, float *window, uint32_t length)
288 {
289 return (pvfile_write_32(p, window, (int32_t) length));
290 }
291
pvoc_readWindow(PVOCFILE * p,float * window,uint32_t length)292 static int32_t pvoc_readWindow(PVOCFILE *p, float *window, uint32_t length)
293 {
294 return (pvfile_read_32(p, window, (int32_t) length) != (int32_t) length);
295 }
296
pvoc_errorstr(CSOUND * csound)297 const char *pvoc_errorstr(CSOUND *csound)
298 {
299 int32_t i = -(csound->pvErrorCode);
300
301 if (UNLIKELY(i < 0 || i > 42)) i = 1;
302 return (const char *) Str(pvErrorStrings[i]);
303 }
304
305 /***** loosely modelled on CDP sfsys ******
306 * This is a static array, but could be made dynamic in an OOP sort of way.
307 * The idea is that all low-level operations and data
308 * are completely hidden from the user, so that internal
309 * format changes can be made with little or no disruption
310 * to the public functions.
311 * But avoiding the full monty of a C++ implementation.
312 *******************************************/
313
init_pvsys(CSOUND * csound)314 int32_t init_pvsys(CSOUND *csound)
315 {
316 if (UNLIKELY(csound->pvNumFiles)) {
317 csound->pvErrorCode = -2;
318 return 0;
319 }
320 csound->pvErrorCode = 0;
321 return 1;
322 }
323
pvsys_getFileHandle(CSOUND * csound,int fd)324 static inline PVOCFILE *pvsys_getFileHandle(CSOUND *csound, int fd)
325 {
326 if (UNLIKELY(fd < 0 || fd >= csound->pvNumFiles))
327 return (PVOCFILE*) NULL;
328 return (PVFILETABLE[fd]);
329 }
330
pvsys_createFileHandle(CSOUND * csound)331 static int pvsys_createFileHandle(CSOUND *csound)
332 {
333 int32_t i;
334 for (i = 0; i < csound->pvNumFiles; i++) {
335 if (PVFILETABLE[i] == NULL)
336 break;
337 }
338 if (i >= csound->pvNumFiles) {
339 PVOCFILE **tmp;
340 int32_t j = i;
341 /* extend table */
342 if (!csound->pvNumFiles) {
343 csound->pvNumFiles = 8;
344 tmp = (PVOCFILE**) csound->Malloc(csound,
345 sizeof(PVOCFILE*) * csound->pvNumFiles);
346 }
347 else {
348 csound->pvNumFiles <<= 1;
349 tmp = (PVOCFILE**) csound->ReAlloc(csound, csound->pvFileTable,
350 sizeof(PVOCFILE*) * csound->pvNumFiles);
351 }
352 if (tmp == NULL)
353 return -1;
354 csound->pvFileTable = (void*) tmp;
355 for ( ; j < csound->pvNumFiles; j++)
356 PVFILETABLE[j] = (PVOCFILE*) NULL;
357 }
358 /* allocate new handle */
359 PVFILETABLE[i] = (PVOCFILE*) csound->Malloc(csound, sizeof(PVOCFILE));
360 if (PVFILETABLE[i] == NULL)
361 return -1;
362 memset(PVFILETABLE[i], 0, sizeof(PVOCFILE));
363 return i;
364 }
365
prepare_pvfmt(WAVEFORMATEX * pfmt,uint32 chans,uint32 srate,pv_stype stype)366 static void prepare_pvfmt(WAVEFORMATEX *pfmt, uint32 chans,
367 uint32 srate, pv_stype stype)
368 {
369 pfmt->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
370 pfmt->nChannels = (uint16_t) chans;
371 pfmt->nSamplesPerSec = srate;
372 switch (stype) {
373 default:
374 case (STYPE_16):
375 pfmt->wBitsPerSample = (uint16_t) 16;
376 pfmt->nBlockAlign = (uint16_t) (chans * 2 * sizeof(char));
377 break;
378 case (STYPE_24):
379 pfmt->wBitsPerSample = (uint16_t) 24;
380 pfmt->nBlockAlign = (uint16_t) (chans * 3 * sizeof(char));
381 break;
382 case (STYPE_32):
383 case (STYPE_IEEE_FLOAT):
384 pfmt->wBitsPerSample = (uint16_t) 32;
385 pfmt->nBlockAlign = (uint16_t) (chans * 4 * sizeof(char));
386 break;
387 }
388 pfmt->nAvgBytesPerSec = pfmt->nBlockAlign * srate;
389 /* if we have extended WindowParam fields, or something,
390 will need to adjust this */
391 pfmt->cbSize = 62;
392 }
393
394 /* lots of different ways of doing this!
395 * we will need one in the form:
396 * int pvoc_fmtcreate(const char *fname, PVOCDATA *p_pvfmt,
397 * WAVEFORMATEX *p_wvfmt);
398 */
399
400 /* a simple minimalist function to begin with!*/
401 /* set D to 0, and/or dwWinlen to 0, to use internal default */
402 /* fWindow points to userdef window, or is NULL */
403 /* NB currently this does not enforce a soundfile extension; */
404 /* probably it should... */
405
pvoc_createfile(CSOUND * csound,const char * filename,uint32 fftlen,uint32 overlap,uint32 chans,uint32 format,int32_t srate,int32_t stype,int32_t wtype,float wparam,float * fWindow,uint32 dwWinlen)406 int32_t pvoc_createfile(CSOUND *csound, const char *filename,
407 uint32 fftlen, uint32 overlap,
408 uint32 chans, uint32 format,
409 int32_t srate, int32_t stype, int32_t wtype,
410 float wparam, float *fWindow, uint32 dwWinlen)
411 {
412 int32_t fd;
413 int32_t N, D;
414 char *pname;
415 PVOCFILE *p = NULL;
416 float winparam = 0.0f;
417
418 N = fftlen; /* keep the CARL varnames for now */
419 D = overlap;
420 csound->pvErrorCode = -1;
421
422 if (UNLIKELY(N == 0 || (int32_t) chans <= 0 || filename == NULL || D > N)) {
423 csound->pvErrorCode = -3;
424 return -1;
425 }
426 if (UNLIKELY(/*format < PVOC_AMP_FREQ ||*/ format > PVOC_COMPLEX)) {
427 csound->pvErrorCode = -4;
428 return -1;
429 }
430 if (UNLIKELY(!(wtype >= PVOC_DEFAULT && wtype <= PVOC_CUSTOM))) {
431 csound->pvErrorCode = -5;
432 return -1;
433 }
434
435 /* load it, but can not write until we have a PVXW chunk definition... */
436 if (wtype == PVOC_CUSTOM) {
437
438 }
439
440 else if (wtype == PVOC_DEFAULT)
441 wtype = PVOC_HAMMING;
442
443 else if (wtype == PVOC_KAISER)
444 if (wparam != 0.0f)
445 winparam = wparam;
446 /* will need an internal default for window parameters... */
447
448 fd = pvsys_createFileHandle(csound);
449 if (UNLIKELY(fd < 0)) {
450 csound->pvErrorCode = -6;
451 return -1;
452 }
453 p = pvsys_getFileHandle(csound, fd);
454 pname = (char *) csound->Malloc(csound, strlen(filename) + 1);
455 strcpy(pname, filename);
456 p->customWindow = NULL;
457
458 /* setup rendering inforamtion */
459 prepare_pvfmt(&p->fmtdata, chans, srate, stype);
460 p->pvdata.wWordFormat = PVOC_IEEE_FLOAT;
461 p->pvdata.wAnalFormat = (uint16_t) format;
462 if (stype == STYPE_IEEE_FLOAT)
463 p->pvdata.wSourceFormat = WAVE_FORMAT_IEEE_FLOAT;
464 else
465 p->pvdata.wSourceFormat = WAVE_FORMAT_PCM;
466 p->pvdata.wWindowType = wtype;
467 p->pvdata.nAnalysisBins = (N >> 1) + 1;
468 if (dwWinlen == 0)
469 p->pvdata.dwWinlen = N;
470 else
471 p->pvdata.dwWinlen = dwWinlen;
472 if (D == 0)
473 p->pvdata.dwOverlap = N / 8;
474 else
475 p->pvdata.dwOverlap = D;
476 p->pvdata.dwFrameAlign = p->pvdata.nAnalysisBins * 2 * sizeof(float);
477 p->pvdata.fAnalysisRate = (float) srate / (float) p->pvdata.dwOverlap;
478 p->pvdata.fWindowParam = winparam;
479 if (fWindow != NULL) {
480 p->customWindow = csound->Malloc(csound, dwWinlen * sizeof(float));
481 memcpy(p->customWindow, fWindow, dwWinlen * sizeof(float));
482 }
483
484 p->fd = csound->FileOpen2(csound, &(p->fp), CSFILE_STD, filename, "wb",
485 "", CSFTYPE_PVCEX, 0);
486 if (UNLIKELY(p->fd == NULL)) {
487 csound->Free(csound, pname);
488 if (p->customWindow)
489 csound->Free(csound, p->customWindow);
490 csound->Free(csound, p);
491 PVFILETABLE[fd] = NULL;
492 csound->pvErrorCode = -7;
493 return -1;
494 }
495 p->name = pname;
496
497 if (pvoc_writeheader(csound, p) != 0) {
498 csound->FileClose(csound, p->fd);
499 (void)remove(p->name);
500 csound->Free(csound, p->name);
501 if (p->customWindow)
502 csound->Free(csound, p->customWindow);
503 csound->Free(csound, p);
504 PVFILETABLE[fd] = NULL;
505 return -1;
506 }
507
508 csound->pvErrorCode = 0;
509 return fd;
510 }
511
pvoc_openfile(CSOUND * csound,const char * filename,void * data_,void * fmt_)512 int32_t pvoc_openfile(CSOUND *csound,
513 const char *filename, void *data_, void *fmt_)
514 {
515 WAVEFORMATPVOCEX wfpx;
516 char *pname;
517 PVOCFILE *p = NULL;
518 int32_t fd;
519 PVOCDATA *data = (PVOCDATA *) data_;
520 WAVEFORMATEX *fmt = (WAVEFORMATEX *) fmt_;
521
522 csound->pvErrorCode = -1;
523 if (UNLIKELY(data == NULL || fmt == NULL)) {
524 csound->pvErrorCode = -8;
525 return -1;
526 }
527 fd = pvsys_createFileHandle(csound);
528 if (UNLIKELY(fd < 0)) {
529 csound->pvErrorCode = -6;
530 return -1;
531 }
532 p = pvsys_getFileHandle(csound, fd);
533
534 p->customWindow = NULL;
535 p->fd = csound->FileOpen2(csound, &(p->fp), CSFILE_STD, filename,
536 "rb", "SADIR", CSFTYPE_PVCEX, 0);
537 if (UNLIKELY(p->fd == NULL)) {
538 csound->pvErrorCode = -9;
539 csound->Free(csound, p);
540 PVFILETABLE[fd] = NULL;
541 return -1;
542 }
543 pname = (char*) csound->Malloc(csound, strlen(filename) + 1);
544 strcpy(pname, filename);
545 p->name = pname;
546 p->readonly = 1;
547
548 if (UNLIKELY(pvoc_readheader(csound, p, &wfpx) != 0)) {
549 csound->FileClose(csound, p->fd);
550 csound->Free(csound, p->name);
551 if (p->customWindow)
552 csound->Free(csound, p->customWindow);
553 csound->Free(csound, p);
554 PVFILETABLE[fd] = NULL;
555 return -1;
556 }
557 memcpy(data, &(wfpx.data), sizeof(PVOCDATA));
558 memcpy(fmt, &(wfpx.wxFormat.Format), SIZEOF_WFMTEX);
559
560 csound->pvErrorCode = 0;
561 return fd;
562 }
563
pvoc_readfmt(CSOUND * csound,PVOCFILE * p,WAVEFORMATPVOCEX * pWfpx)564 static int32_t pvoc_readfmt(CSOUND *csound, PVOCFILE *p, WAVEFORMATPVOCEX *pWfpx)
565 {
566 WAVEFORMATEXTENSIBLE *wxfmt = &(pWfpx->wxFormat);
567 WAVEFORMATEX *fmt = &(wxfmt->Format);
568 int32_t err = 0;
569
570 memset(pWfpx, 0, sizeof(WAVEFORMATPVOCEX));
571 err |= (pvfile_read_16(p, &(fmt->wFormatTag), 1L) != 1L);
572 err |= (pvfile_read_16(p, &(fmt->nChannels), 1L) != 1L);
573 err |= (pvfile_read_32(p, &(fmt->nSamplesPerSec), 1L) != 1L);
574 err |= (pvfile_read_32(p, &(fmt->nAvgBytesPerSec), 1L) != 1L);
575 err |= (pvfile_read_16(p, &(fmt->nBlockAlign), 1L) != 1L);
576 err |= (pvfile_read_16(p, &(fmt->wBitsPerSample), 1L) != 1L);
577 err |= (pvfile_read_16(p, &(fmt->cbSize), 1L) != 1L);
578 if (UNLIKELY(err)) {
579 csound->pvErrorCode = -10;
580 return err;
581 }
582 /* the first clues this is pvx format...*/
583 if (UNLIKELY(fmt->wFormatTag != WAVE_FORMAT_EXTENSIBLE)) {
584 csound->pvErrorCode = -11;
585 return -1;
586 }
587 if (UNLIKELY(fmt->cbSize != 62)) {
588 csound->pvErrorCode = -12;
589 return -1;
590 }
591 err |= (pvfile_read_16(p, &(wxfmt->Samples.wValidBitsPerSample), 1L) != 1L);
592 err |= (pvfile_read_32(p, &(wxfmt->dwChannelMask), 1L) != 1L);
593 err |= (pvfile_read_32(p, &(wxfmt->SubFormat.Data1), 1L) != 1L);
594 err |= (pvfile_read_16(p, &(wxfmt->SubFormat.Data2), 1L) != 1L);
595 err |= (pvfile_read_16(p, &(wxfmt->SubFormat.Data3), 1L) != 1L);
596 err |= ((int32_t) fread(&(wxfmt->SubFormat.Data4[0]), 1, 8, p->fp) != 8);
597 if (UNLIKELY(err)) {
598 csound->pvErrorCode = -13;
599 return -1;
600 }
601 /* ... but this is the clincher */
602 if (UNLIKELY(!compare_guids(&(pWfpx->wxFormat.SubFormat),
603 &KSDATAFORMAT_SUBTYPE_PVOC))) {
604 csound->pvErrorCode = -14;
605 return -1;
606 }
607 err |= (pvfile_read_32(p, &(pWfpx->dwVersion), 1L) != 1L);
608 err |= (pvfile_read_32(p, &(pWfpx->dwDataSize), 1L) != 1L);
609 err |= (pvfile_read_16(p, &(pWfpx->data.wWordFormat), 1L) != 1L);
610 err |= (pvfile_read_16(p, &(pWfpx->data.wAnalFormat), 1L) != 1L);
611 err |= (pvfile_read_16(p, &(pWfpx->data.wSourceFormat), 1L) != 1L);
612 err |= (pvfile_read_16(p, &(pWfpx->data.wWindowType), 1L) != 1L);
613 err |= (pvfile_read_32(p, &(pWfpx->data.nAnalysisBins), 1L) != 1L);
614 err |= (pvfile_read_32(p, &(pWfpx->data.dwWinlen), 1L) != 1L);
615 err |= (pvfile_read_32(p, &(pWfpx->data.dwOverlap), 1L) != 1L);
616 err |= (pvfile_read_32(p, &(pWfpx->data.dwFrameAlign), 1L) != 1L);
617 err |= (pvfile_read_32(p, &(pWfpx->data.fAnalysisRate), 1L) != 1L);
618 err |= (pvfile_read_32(p, &(pWfpx->data.fWindowParam), 1L) != 1L);
619 if (UNLIKELY(err)) {
620 csound->pvErrorCode = -15;
621 return -1;
622 }
623 if (UNLIKELY(pWfpx->dwVersion != PVX_VERSION)) {
624 csound->pvErrorCode = -16;
625 return -1;
626 }
627
628
629 return 0;
630 }
631
pvoc_readheader(CSOUND * csound,PVOCFILE * p,WAVEFORMATPVOCEX * pWfpx)632 static int32_t pvoc_readheader(CSOUND *csound, PVOCFILE *p,
633 WAVEFORMATPVOCEX *pWfpx)
634 {
635 char tag[5];
636 uint32_t size;
637 uint32_t riffsize;
638 int32_t fmtseen = 0, windowseen = 0;
639
640 if (UNLIKELY(pvfile_read_tag(p, &(tag[0])) != 0 ||
641 strcmp(tag, "RIFF") != 0 ||
642 pvfile_read_32(p, &size, 1L) != 1L)) {
643 csound->pvErrorCode = -17;
644 return -1;
645 }
646 if (UNLIKELY(size < 24 * sizeof(uint32_t) + SIZEOF_FMTPVOCEX)) {
647 csound->pvErrorCode = -19;
648 return -1;
649 }
650 riffsize = size;
651 if (UNLIKELY(pvfile_read_tag(p, &(tag[0])) != 0 || strcmp(tag, "WAVE") != 0)) {
652 csound->pvErrorCode = -20;
653 return -1;
654 }
655 riffsize -= sizeof(uint32_t);
656 /* loop for chunks */
657 while (riffsize > (uint32_t) 0) {
658 if (UNLIKELY(pvfile_read_tag(p, &(tag[0])) != 0 ||
659 pvfile_read_32(p, &size, 1L) != 1L)) {
660 csound->pvErrorCode = -17;
661 return -1;
662 }
663 riffsize -= 2 * sizeof(uint32_t);
664 if (strcmp(tag, "fmt ") == 0) {
665 /* bail out if not a pvoc file: not trying to read all WAVE formats!*/
666 if (UNLIKELY((int32_t) size < (int32_t) SIZEOF_FMTPVOCEX)) {
667 csound->pvErrorCode = -14;
668 return -1;
669 }
670 if (UNLIKELY(pvoc_readfmt(csound, p, pWfpx) != 0)) {
671 csound->pvErrorCode = -21;
672 return -1;
673 }
674 riffsize -= SIZEOF_FMTPVOCEX;
675 fmtseen = 1;
676 memcpy(&(p->fmtdata), &(pWfpx->wxFormat), SIZEOF_WFMTEX);
677 memcpy(&(p->pvdata), &(pWfpx->data), sizeof(PVOCDATA));
678 }
679 else if (strcmp(tag, "PVXW") == 0) {
680 if (UNLIKELY(!fmtseen)) {
681 csound->pvErrorCode = -22;
682 return -1;
683 }
684 if (UNLIKELY(p->pvdata.wWindowType != PVOC_CUSTOM)) {
685 /* whaddayado? can you warn the user and continue? */
686 csound->pvErrorCode = -23;
687 return -1;
688 }
689 p->customWindow = csound->Malloc(csound, p->pvdata.dwWinlen * sizeof(float));
690 if (UNLIKELY(pvoc_readWindow(p,
691 p->customWindow, p->pvdata.dwWinlen) != 0)) {
692 csound->pvErrorCode = -24;
693 return -1;
694 }
695 windowseen = 1;
696 }
697 else if (strcmp(tag, "data") == 0) {
698 if (UNLIKELY((uint32_t) riffsize != size)) {
699 csound->pvErrorCode = -25;
700 return -1;
701 }
702 if (UNLIKELY(!fmtseen)) {
703 csound->pvErrorCode = -26;
704 return -1;
705 }
706 if (p->pvdata.wWindowType == PVOC_CUSTOM) {
707 if (UNLIKELY(!windowseen)) {
708 csound->pvErrorCode = -27;
709 return -1;
710 }
711 }
712 p->datachunkoffset = (int32_t) ftell(p->fp);
713 p->curpos = p->datachunkoffset;
714 /* not m/c frames, for now */
715 p->nFrames = size / p->pvdata.dwFrameAlign;
716 return 0;
717 }
718 else {
719 /* skip any unknown chunks */
720 riffsize -= 2 * sizeof(uint32_t);
721 if (UNLIKELY(fseek(p->fp, (int32_t) size, SEEK_CUR) != 0)) {
722 csound->pvErrorCode = -28;
723 return -1;
724 }
725 riffsize -= size;
726 }
727 }
728 /* if here, something very wrong! */
729 csound->pvErrorCode = -29;
730 return -1;
731 }
732
pvoc_writeheader(CSOUND * csound,PVOCFILE * p)733 static int32_t pvoc_writeheader(CSOUND *csound, PVOCFILE *p)
734 {
735 uint32_t version, size = (uint32_t) 0;
736 int32_t err = 0;
737
738 err |= pvfile_write_tag(p, "RIFF");
739 err |= pvfile_write_32(p, &size, 1L);
740 if (UNLIKELY(err)) {
741 csound->pvErrorCode = -30;
742 return -1;
743 }
744 size = SIZEOF_WFMTEX + sizeof(uint16_t)
745 + sizeof(uint32_t)
746 + sizeof(GUID)
747 + 2 * sizeof(uint32_t)
748 + sizeof(PVOCDATA);
749 err |= pvfile_write_tag(p, "WAVE");
750 err |= pvfile_write_tag(p, "fmt ");
751 err |= pvfile_write_32(p, &size, 1L);
752 if (UNLIKELY(err)) {
753 csound->pvErrorCode = -30;
754 return -1;
755 }
756 if (UNLIKELY(write_fmt(p) != 0)) {
757 csound->pvErrorCode = -31;
758 return -1;
759 }
760 if (UNLIKELY(pvfile_write_16(p, &(p->fmtdata.wBitsPerSample), 1L) != 0)) {
761 csound->pvErrorCode = -31;
762 return -1;
763 }
764 /* we will take this from a WAVE_EX file, in due course */
765 size = 0; /* dwChannelMask */
766 if (UNLIKELY(pvfile_write_32(p, &size, 1L) != 0)) {
767 csound->pvErrorCode = -31;
768 return -1;
769 }
770 if (UNLIKELY(write_guid(p, &KSDATAFORMAT_SUBTYPE_PVOC) != 0)) {
771 csound->pvErrorCode = -31;
772 return -1;
773 }
774 version = (uint32_t) 1;
775 size = sizeof(PVOCDATA);
776 if (UNLIKELY(pvfile_write_32(p, &version, 1L) != 0 ||
777 pvfile_write_32(p, &size, 1L) != 0)) {
778 csound->pvErrorCode = -31;
779 return -1;
780 }
781 if (UNLIKELY(write_pvocdata(p) != 0)) {
782 csound->pvErrorCode = -31;
783 return -1;
784 }
785 /* VERY experimental; may not even be a good idea...*/
786 if (p->customWindow) {
787 if (UNLIKELY(pvfile_write_tag(p, "PVXW") != 0)) {
788 csound->pvErrorCode = -30;
789 return -1;
790 }
791 size = p->pvdata.dwWinlen * sizeof(float);
792 if (UNLIKELY(pvfile_write_32(p, &size, 1L) != 0)) {
793 csound->pvErrorCode = -30;
794 return -1;
795 }
796 if (UNLIKELY(pvoc_writeWindow(p, p->customWindow, p->pvdata.dwWinlen) != 0)) {
797 csound->pvErrorCode = -32;
798 return -1;
799 }
800 }
801 /* no other chunks to write yet! */
802 if (UNLIKELY(pvfile_write_tag(p, "data") != 0)) {
803 csound->pvErrorCode = -30;
804 return -1;
805 }
806 /* we need to update size later on... */
807 size = (uint32_t) 0;
808 if (UNLIKELY(pvfile_write_32(p, &size, 1L) != 0)) {
809 csound->pvErrorCode = -30;
810 return -1;
811 }
812 p->datachunkoffset = (int32_t) ftell(p->fp);
813 p->curpos = p->datachunkoffset;
814
815 return 0;
816 }
817
pvoc_updateheader(CSOUND * csound,int32_t ofd)818 static int32_t pvoc_updateheader(CSOUND *csound, int32_t ofd)
819 {
820 PVOCFILE *p = pvsys_getFileHandle(csound, ofd);
821 uint32_t riffsize, datasize;
822
823 if (UNLIKELY(p == NULL)) {
824 csound->pvErrorCode = -38;
825 return 0;
826 }
827 if (UNLIKELY(fseek(p->fp,
828 (int32_t) (p->datachunkoffset - sizeof(uint32_t)), SEEK_SET)
829 != 0)) {
830 csound->pvErrorCode = -33;
831 return 0;
832 }
833 datasize = p->curpos - p->datachunkoffset;
834 if (UNLIKELY(pvfile_write_32(p, &datasize, 1L) != 0)) {
835 csound->pvErrorCode = -33;
836 return 0;
837 }
838 if (UNLIKELY(fseek(p->fp, (int32_t) sizeof(uint32_t), SEEK_SET) != 0)) {
839 csound->pvErrorCode = -33;
840 return 0;
841 }
842 riffsize = p->curpos - 2 * sizeof(uint32_t);
843 if (UNLIKELY(pvfile_write_32(p, &riffsize, 1L) != 0)) {
844 csound->pvErrorCode = -34;
845 return 0;
846 }
847 if (UNLIKELY(fseek(p->fp, 0L, SEEK_END) != 0)) {
848 csound->pvErrorCode = -35;
849 return 0;
850 }
851
852 return 1;
853 }
854
pvoc_closefile(CSOUND * csound,int32_t ofd)855 int32_t pvoc_closefile(CSOUND *csound, int32_t ofd)
856 {
857 PVOCFILE *p = pvsys_getFileHandle(csound, ofd);
858 int32_t rc = 1;
859
860 csound->pvErrorCode = 0;
861 if (UNLIKELY(p == NULL)) {
862 csound->pvErrorCode = -36;
863 return 0;
864 }
865 if (UNLIKELY(p->fd == NULL)) {
866 csound->pvErrorCode = -37;
867 csound->Free(csound, p);
868 PVFILETABLE[ofd] = NULL;
869 return 0;
870 }
871 if (!p->readonly)
872 if (!pvoc_updateheader(csound, ofd))
873 rc = 0;
874
875 csound->FileClose(csound, p->fd);
876 if (p->to_delete && !p->readonly)
877 (void)remove(p->name);
878 csound->Free(csound, p->name);
879 csound->Free(csound, p->customWindow);
880 csound->Free(csound, p);
881 PVFILETABLE[ofd] = NULL;
882
883 return rc;
884 }
885
886 /* does not directly address m/c streams, or alternative numeric formats, yet...
887 * so for m/c files, write each frame in turn, for each channel.
888 * The format requires multi-channel frames to be interleaved in the usual way:
889 * if nChannels= 4, the file will contain:
890 * frame[0][0],frame[0][1],frame[0][2],frame[0][3],frme[1][0],frame[1][1].....
891 *
892 * The idea is to offer e.g. a floats version and a longs version ONLY, but
893 * independently of the underlying representation, so that the user can write
894 * a floats block, even though the underlying format might be longs or doubles.
895 * Most importantly, the user does not have to deal with byte-reversal, which
896 * would otherwise always be the case it the user had direct access to the file.
897 *
898 * So these functions are the most likely to change over time!.
899 *
900 * return 0 for error, 1 for success. This could change....
901 */
pvoc_putframes(CSOUND * csound,int32_t ofd,const float * frame,int32_t numframes)902 int32_t pvoc_putframes(CSOUND *csound, int32_t ofd, const float *frame,
903 int32_t numframes)
904 {
905 PVOCFILE *p = pvsys_getFileHandle(csound, ofd);
906 int32_t towrite; /* count in 'words' */
907
908 if (UNLIKELY(p == NULL)) {
909 csound->pvErrorCode = -38;
910 return 0;
911 }
912 if (UNLIKELY(p->fd == NULL)) {
913 csound->pvErrorCode = -37;
914 return 0;
915 }
916 /* NB doubles not supported yet */
917 towrite = (int32_t) p->pvdata.nAnalysisBins * 2L * numframes;
918 if (UNLIKELY(pvfile_write_32(p, (void*) frame, towrite) != 0)) {
919 csound->pvErrorCode = -39;
920 return 0;
921 }
922 p->FramePos += numframes;
923 p->curpos += towrite * sizeof(float);
924 return 1;
925 }
926
927 /* Simplistic read function
928 * best practice here is to read nChannels frames
929 * return -1 for error, 0 for EOF, else numframes read
930 */
pvoc_getframes(CSOUND * csound,int32_t ifd,float * frames,uint32 nframes)931 int32_t pvoc_getframes(CSOUND *csound, int32_t ifd, float *frames,
932 uint32 nframes)
933 {
934 PVOCFILE *p = pvsys_getFileHandle(csound, ifd);
935 int32_t toread, got;
936
937 if (UNLIKELY(p == NULL)) {
938 csound->pvErrorCode = -38;
939 return -1;
940 }
941 if (UNLIKELY(p->fd == NULL)) {
942 csound->pvErrorCode = -37;
943 return -1;
944 }
945 toread = (int32_t) p->pvdata.nAnalysisBins * 2L * (int32_t) nframes;
946 got = pvfile_read_32(p, frames, toread);
947 if (got != toread) {
948 if (UNLIKELY(ferror(p->fp))) {
949 csound->pvErrorCode = -40;
950 return -1;
951 }
952 p->curpos += (int32_t) (got * sizeof(float));
953 got = got / (int32_t) (p->pvdata.nAnalysisBins * 2);
954 p->FramePos += got;
955 return (int32_t) got;
956 }
957 p->curpos += (toread * sizeof(float));
958 p->FramePos += (int32_t) nframes;
959
960 return (int32_t) nframes;
961 }
962
pvoc_fseek(CSOUND * csound,int32_t ifd,int32_t offset)963 int32_t pvoc_fseek(CSOUND *csound, int32_t ifd, int32_t offset)
964 {
965 PVOCFILE *p = pvsys_getFileHandle(csound, ifd);
966 int32_t pos, skipframes, skipsize;
967
968 if (UNLIKELY(p == NULL)) {
969 csound->pvErrorCode = -38;
970 return -1;
971 }
972 if (UNLIKELY(p->fd == NULL)) {
973 csound->pvErrorCode = -37;
974 return -1;
975 }
976 if (offset == 1)
977 skipframes = (int32_t) p->fmtdata.nChannels;
978 else skipframes = offset;
979 skipsize = p->pvdata.dwFrameAlign * skipframes;
980
981 pos = p->datachunkoffset + skipsize;
982 if (UNLIKELY(fseek(p->fp, (int32_t) pos, SEEK_SET) != 0)) {
983 csound->pvErrorCode = -41;
984 return -1;
985 }
986 p->curpos = pos;
987 p->FramePos = skipframes;
988
989 return 0;
990 }
991
992 /* may be more to do in here later on */
993
pvsys_release(CSOUND * csound)994 int32_t pvsys_release(CSOUND *csound)
995 {
996 int32_t i;
997
998 csound->pvErrorCode = 0;
999 for (i = 0; i < csound->pvNumFiles; i++) {
1000 if (pvsys_getFileHandle(csound, i) != NULL) {
1001 if (UNLIKELY(!pvoc_closefile(csound, i))) {
1002 csound->pvErrorCode = -42;
1003 }
1004 }
1005 }
1006 if (csound->pvNumFiles) {
1007 csound->Free(csound, csound->pvFileTable);
1008 csound->pvFileTable = NULL;
1009 csound->pvNumFiles = 0;
1010 }
1011 return (csound->pvErrorCode == 0 ? 1 : 0);
1012 }
1013
1014 /* return raw framecount: channel-agnostic for now */
1015
pvoc_framecount(CSOUND * csound,int32_t ifd)1016 int32_t pvoc_framecount(CSOUND *csound, int32_t ifd)
1017 {
1018 PVOCFILE *p = pvsys_getFileHandle(csound, ifd);
1019 if (UNLIKELY(p == NULL)) {
1020 csound->pvErrorCode = -38;
1021 return -1;
1022 }
1023 return p->nFrames;
1024 }
1025