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