formats: clarify setting of reverse_bytes
[sox.git] / src / cvsd.c
blob9e2bbdef1c7827d89e1fd4c641b494b426cb5d70
1 /* libSoX CVSD (Continuously Variable Slope Delta modulation)
2 * conversion routines
4 * The CVSD format is described in the MIL Std 188 113, which is
5 * available from http://bbs.itsi.disa.mil:5580/T3564
7 * Copyright (C) 1996
8 * Thomas Sailer (sailer@ife.ee.ethz.ch) (HB9JNX/AE4WA)
9 * Swiss Federal Institute of Technology, Electronics Lab
11 * This library is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or (at
14 * your option) any later version.
16 * This library is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
19 * General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this library; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 * Change History:
27 * June 1, 1998 - Chris Bagwell (cbagwell@sprynet.com)
28 * Fixed compile warnings reported by Kjetil Torgrim Homme
29 * <kjetilho@ifi.uio.no>
31 * June 20, 2006 - Kimberly Rockwell (pyxis13317 (at) yahoo.com)
32 * Speed optimization: Unrolled float_conv() loop in seperate
33 * functions for encoding and decoding. 15% speed up decoding.
35 * Aug. 24, 2009 - P. Chaintreuil (sox-cvsd-peep (at) parallaxshift.com)
36 * Speed optimization: Replaced calls to memmove() with a
37 * mirrored circular buffer. This doubles the size of the
38 * dec.output_filter (48 -> 96 floats) and enc.input_filter
39 * (16 -> 32 floats), but keeps the memory from having to
40 * be copied so many times. 56% speed increase decoding;
41 * less than 5% encoding speed increase.
44 #include "sox_i.h"
45 #include "cvsd.h"
46 #include "cvsdfilt.h"
48 #include <string.h>
49 #include <time.h>
51 /* ---------------------------------------------------------------------- */
53 * private data structures
56 typedef cvsd_priv_t priv_t;
58 static int debug_count = 0;
60 /* ---------------------------------------------------------------------- */
62 /* This float_conv() function is not used as more specialized/optimized
63 * versions exist below. However, those new versions are tied to
64 * very percise filters defined in cvsdfilt.h. If those are modified
65 * or different filters are found to be required, this function may
66 * be needed. Thus I leave it here for possible future use, but commented
67 * out to avoid compiler warnings about it not being used.
68 static float float_conv(float const *fp1, float const *fp2,int n)
70 float res = 0;
71 for(; n > 0; n--)
72 res += (*fp1++) * (*fp2++);
73 return res;
77 static float float_conv_enc(float const *fp1, float const *fp2)
79 /* This is a specialzed version of float_conv() for encoding
80 * which simply assumes a CVSD_ENC_FILTERLEN (16) length of
81 * the two arrays and unrolls that loop.
83 * fp1 should be the enc.input_filter array and must be
84 * CVSD_ENC_FILTERLEN (16) long.
86 * fp2 should be one of the enc_filter_xx_y() tables listed
87 * in cvsdfilt.h. At minimum, fp2 must be CVSD_ENC_FILTERLEN
88 * (16) entries long.
90 float res = 0;
92 /* unrolling loop */
93 res += fp1[0] * fp2[0];
94 res += fp1[1] * fp2[1];
95 res += fp1[2] * fp2[2];
96 res += fp1[3] * fp2[3];
97 res += fp1[4] * fp2[4];
98 res += fp1[5] * fp2[5];
99 res += fp1[6] * fp2[6];
100 res += fp1[7] * fp2[7];
101 res += fp1[8] * fp2[8];
102 res += fp1[9] * fp2[9];
103 res += fp1[10] * fp2[10];
104 res += fp1[11] * fp2[11];
105 res += fp1[12] * fp2[12];
106 res += fp1[13] * fp2[13];
107 res += fp1[14] * fp2[14];
108 res += fp1[15] * fp2[15];
110 return res;
113 static float float_conv_dec(float const *fp1, float const *fp2)
115 /* This is a specialzed version of float_conv() for decoding
116 * which assumes a specific length and structure to the data
117 * in fp2.
119 * fp1 should be the dec.output_filter array and must be
120 * CVSD_DEC_FILTERLEN (48) long.
122 * fp2 should be one of the dec_filter_xx() tables listed
123 * in cvsdfilt.h. fp2 is assumed to be CVSD_DEC_FILTERLEN
124 * (48) entries long, is assumed to have 0.0 in the last
125 * entry, and is a symmetrical mirror around fp2[23] (ie,
126 * fp2[22] == fp2[24], fp2[0] == fp2[47], etc).
128 float res = 0;
130 /* unrolling loop, also taking advantage of the symmetry
131 * of the sampling rate array*/
132 res += (fp1[0] + fp1[46]) * fp2[0];
133 res += (fp1[1] + fp1[45]) * fp2[1];
134 res += (fp1[2] + fp1[44]) * fp2[2];
135 res += (fp1[3] + fp1[43]) * fp2[3];
136 res += (fp1[4] + fp1[42]) * fp2[4];
137 res += (fp1[5] + fp1[41]) * fp2[5];
138 res += (fp1[6] + fp1[40]) * fp2[6];
139 res += (fp1[7] + fp1[39]) * fp2[7];
140 res += (fp1[8] + fp1[38]) * fp2[8];
141 res += (fp1[9] + fp1[37]) * fp2[9];
142 res += (fp1[10] + fp1[36]) * fp2[10];
143 res += (fp1[11] + fp1[35]) * fp2[11];
144 res += (fp1[12] + fp1[34]) * fp2[12];
145 res += (fp1[13] + fp1[33]) * fp2[13];
146 res += (fp1[14] + fp1[32]) * fp2[14];
147 res += (fp1[15] + fp1[31]) * fp2[15];
148 res += (fp1[16] + fp1[30]) * fp2[16];
149 res += (fp1[17] + fp1[29]) * fp2[17];
150 res += (fp1[18] + fp1[28]) * fp2[18];
151 res += (fp1[19] + fp1[27]) * fp2[19];
152 res += (fp1[20] + fp1[26]) * fp2[20];
153 res += (fp1[21] + fp1[25]) * fp2[21];
154 res += (fp1[22] + fp1[24]) * fp2[22];
155 res += (fp1[23]) * fp2[23];
157 return res;
160 /* ---------------------------------------------------------------------- */
162 * some remarks about the implementation of the CVSD decoder
163 * the principal integrator is integrated into the output filter
164 * to achieve this, the coefficients of the output filter are multiplied
165 * with (1/(1-1/z)) in the initialisation code.
166 * the output filter must have a sharp zero at f=0 (i.e. the sum of the
167 * filter parameters must be zero). This prevents an accumulation of
168 * DC voltage at the principal integration.
170 /* ---------------------------------------------------------------------- */
172 static void cvsdstartcommon(sox_format_t * ft)
174 priv_t *p = (priv_t *) ft->priv;
176 p->cvsd_rate = (ft->signal.rate <= 24000) ? 16000 : 32000;
177 ft->signal.rate = 8000;
178 ft->signal.channels = 1;
179 lsx_rawstart(ft, sox_true, sox_false, sox_true, SOX_ENCODING_CVSD, 1);
181 * initialize the decoder
183 p->com.overload = 0x5;
184 p->com.mla_int = 0;
186 * timeconst = (1/e)^(200 / SR) = exp(-200/SR)
187 * SR is the sampling rate
189 p->com.mla_tc0 = exp((-200.0)/((float)(p->cvsd_rate)));
191 * phase_inc = 32000 / SR
193 p->com.phase_inc = 32000 / p->cvsd_rate;
195 * initialize bit shift register
197 p->bit.shreg = p->bit.cnt = 0;
198 p->bit.mask = 1;
200 * count the bytes written
202 p->bytes_written = 0;
203 p->com.v_min = 1;
204 p->com.v_max = -1;
205 lsx_report("cvsd: bit rate %dbit/s, bits from %s", p->cvsd_rate,
206 ft->encoding.reverse_bits ? "msb to lsb" : "lsb to msb");
209 /* ---------------------------------------------------------------------- */
211 int lsx_cvsdstartread(sox_format_t * ft)
213 priv_t *p = (priv_t *) ft->priv;
214 float *fp1;
215 int i;
217 cvsdstartcommon(ft);
219 p->com.mla_tc1 = 0.1 * (1 - p->com.mla_tc0);
220 p->com.phase = 0;
222 * initialize the output filter coeffs (i.e. multiply
223 * the coeffs with (1/(1-1/z)) to achieve integration
224 * this is now done in the filter parameter generation utility
227 * zero the filter
229 for(fp1 = p->c.dec.output_filter, i = CVSD_DEC_FILTERLEN*2; i > 0; i--)
230 *fp1++ = 0;
231 /* initialize mirror circular buffer offset to anything sane. */
232 p->c.dec.offset = CVSD_DEC_FILTERLEN - 1;
234 return (SOX_SUCCESS);
237 /* ---------------------------------------------------------------------- */
239 int lsx_cvsdstartwrite(sox_format_t * ft)
241 priv_t *p = (priv_t *) ft->priv;
242 float *fp1;
243 int i;
245 cvsdstartcommon(ft);
247 p->com.mla_tc1 = 0.1 * (1 - p->com.mla_tc0);
248 p->com.phase = 4;
250 * zero the filter
252 for(fp1 = p->c.enc.input_filter, i = CVSD_ENC_FILTERLEN*2; i > 0; i--)
253 *fp1++ = 0;
254 p->c.enc.recon_int = 0;
255 /* initialize mirror circular buffer offset to anything sane. */
256 p->c.enc.offset = CVSD_ENC_FILTERLEN - 1;
258 return(SOX_SUCCESS);
261 /* ---------------------------------------------------------------------- */
263 int lsx_cvsdstopwrite(sox_format_t * ft)
265 priv_t *p = (priv_t *) ft->priv;
267 if (p->bit.cnt) {
268 lsx_writeb(ft, p->bit.shreg);
269 p->bytes_written++;
271 lsx_debug("cvsd: min slope %f, max slope %f",
272 p->com.v_min, p->com.v_max);
274 return (SOX_SUCCESS);
277 /* ---------------------------------------------------------------------- */
279 int lsx_cvsdstopread(sox_format_t * ft)
281 priv_t *p = (priv_t *) ft->priv;
283 lsx_debug("cvsd: min value %f, max value %f",
284 p->com.v_min, p->com.v_max);
286 return(SOX_SUCCESS);
289 /* ---------------------------------------------------------------------- */
291 size_t lsx_cvsdread(sox_format_t * ft, sox_sample_t *buf, size_t nsamp)
293 priv_t *p = (priv_t *) ft->priv;
294 size_t done = 0;
295 float oval;
297 while (done < nsamp) {
298 if (!p->bit.cnt) {
299 if (lsx_read_b_buf(ft, &(p->bit.shreg), (size_t) 1) != 1)
300 return done;
301 p->bit.cnt = 8;
302 p->bit.mask = 1;
305 * handle one bit
307 p->bit.cnt--;
308 p->com.overload = ((p->com.overload << 1) |
309 (!!(p->bit.shreg & p->bit.mask))) & 7;
310 p->bit.mask <<= 1;
311 p->com.mla_int *= p->com.mla_tc0;
312 if ((p->com.overload == 0) || (p->com.overload == 7))
313 p->com.mla_int += p->com.mla_tc1;
315 /* shift output filter window in mirror cirular buffer. */
316 if (p->c.dec.offset != 0)
317 --p->c.dec.offset;
318 else p->c.dec.offset = CVSD_DEC_FILTERLEN - 1;
319 /* write into both halves of the mirror circular buffer */
320 if (p->com.overload & 1)
322 p->c.dec.output_filter[p->c.dec.offset] = p->com.mla_int;
323 p->c.dec.output_filter[p->c.dec.offset + CVSD_DEC_FILTERLEN] = p->com.mla_int;
325 else
327 p->c.dec.output_filter[p->c.dec.offset] = -p->com.mla_int;
328 p->c.dec.output_filter[p->c.dec.offset + CVSD_DEC_FILTERLEN] = -p->com.mla_int;
332 * check if the next output is due
334 p->com.phase += p->com.phase_inc;
335 if (p->com.phase >= 4) {
336 oval = float_conv_dec(
337 p->c.dec.output_filter + p->c.dec.offset,
338 (p->cvsd_rate < 24000) ?
339 dec_filter_16 : dec_filter_32);
340 lsx_debug_more("input %d %f\n", debug_count, p->com.mla_int);
341 lsx_debug_more("recon %d %f\n", debug_count, oval);
342 debug_count++;
344 if (oval > p->com.v_max)
345 p->com.v_max = oval;
346 if (oval < p->com.v_min)
347 p->com.v_min = oval;
348 *buf++ = (oval * ((float)SOX_SAMPLE_MAX));
349 done++;
351 p->com.phase &= 3;
353 return done;
356 /* ---------------------------------------------------------------------- */
358 size_t lsx_cvsdwrite(sox_format_t * ft, const sox_sample_t *buf, size_t nsamp)
360 priv_t *p = (priv_t *) ft->priv;
361 size_t done = 0;
362 float inval;
364 for(;;) {
366 * check if the next input is due
368 if (p->com.phase >= 4) {
369 if (done >= nsamp)
370 return done;
372 /* shift input filter window in mirror cirular buffer. */
373 if (p->c.enc.offset != 0)
374 --p->c.enc.offset;
375 else p->c.enc.offset = CVSD_ENC_FILTERLEN - 1;
377 /* write into both halves of the mirror circular buffer */
378 p->c.enc.input_filter[p->c.enc.offset] =
379 p->c.enc.input_filter[p->c.enc.offset
380 + CVSD_ENC_FILTERLEN] =
381 (*buf++) /
382 ((float)SOX_SAMPLE_MAX);
383 done++;
385 p->com.phase &= 3;
386 /* insert input filter here! */
387 inval = float_conv_enc(
388 p->c.enc.input_filter + p->c.enc.offset,
389 (p->cvsd_rate < 24000) ?
390 (enc_filter_16[(p->com.phase >= 2)]) :
391 (enc_filter_32[p->com.phase]));
393 * encode one bit
395 p->com.overload = (((p->com.overload << 1) |
396 (inval > p->c.enc.recon_int)) & 7);
397 p->com.mla_int *= p->com.mla_tc0;
398 if ((p->com.overload == 0) || (p->com.overload == 7))
399 p->com.mla_int += p->com.mla_tc1;
400 if (p->com.mla_int > p->com.v_max)
401 p->com.v_max = p->com.mla_int;
402 if (p->com.mla_int < p->com.v_min)
403 p->com.v_min = p->com.mla_int;
404 if (p->com.overload & 1) {
405 p->c.enc.recon_int += p->com.mla_int;
406 p->bit.shreg |= p->bit.mask;
407 } else
408 p->c.enc.recon_int -= p->com.mla_int;
409 if ((++(p->bit.cnt)) >= 8) {
410 lsx_writeb(ft, p->bit.shreg);
411 p->bytes_written++;
412 p->bit.shreg = p->bit.cnt = 0;
413 p->bit.mask = 1;
414 } else
415 p->bit.mask <<= 1;
416 p->com.phase += p->com.phase_inc;
417 lsx_debug_more("input %d %f\n", debug_count, inval);
418 lsx_debug_more("recon %d %f\n", debug_count, p->c.enc.recon_int);
419 debug_count++;
423 /* ---------------------------------------------------------------------- */
425 * DVMS file header
428 /* FIXME: eliminate these 4 functions */
430 static uint32_t get32_le(unsigned char **p)
432 uint32_t val = (((*p)[3]) << 24) | (((*p)[2]) << 16) |
433 (((*p)[1]) << 8) | (**p);
434 (*p) += 4;
435 return val;
438 static uint16_t get16_le(unsigned char **p)
440 unsigned val = (((*p)[1]) << 8) | (**p);
441 (*p) += 2;
442 return val;
445 static void put32_le(unsigned char **p, uint32_t val)
447 *(*p)++ = val & 0xff;
448 *(*p)++ = (val >> 8) & 0xff;
449 *(*p)++ = (val >> 16) & 0xff;
450 *(*p)++ = (val >> 24) & 0xff;
453 static void put16_le(unsigned char **p, unsigned val)
455 *(*p)++ = val & 0xff;
456 *(*p)++ = (val >> 8) & 0xff;
459 struct dvms_header {
460 char Filename[14];
461 unsigned Id;
462 unsigned State;
463 time_t Unixtime;
464 unsigned Usender;
465 unsigned Ureceiver;
466 size_t Length;
467 unsigned Srate;
468 unsigned Days;
469 unsigned Custom1;
470 unsigned Custom2;
471 char Info[16];
472 char extend[64];
473 unsigned Crc;
476 #define DVMS_HEADER_LEN 120
478 /* ---------------------------------------------------------------------- */
480 static int dvms_read_header(sox_format_t * ft, struct dvms_header *hdr)
482 unsigned char hdrbuf[DVMS_HEADER_LEN];
483 unsigned char *pch = hdrbuf;
484 int i;
485 unsigned sum;
487 if (lsx_readbuf(ft, hdrbuf, sizeof(hdrbuf)) != sizeof(hdrbuf))
489 return (SOX_EOF);
491 for(i = sizeof(hdrbuf), sum = 0; i > /*2*/3; i--) /* Deti bug */
492 sum += *pch++;
493 pch = hdrbuf;
494 memcpy(hdr->Filename, pch, sizeof(hdr->Filename));
495 pch += sizeof(hdr->Filename);
496 hdr->Id = get16_le(&pch);
497 hdr->State = get16_le(&pch);
498 hdr->Unixtime = get32_le(&pch);
499 hdr->Usender = get16_le(&pch);
500 hdr->Ureceiver = get16_le(&pch);
501 hdr->Length = get32_le(&pch);
502 hdr->Srate = get16_le(&pch);
503 hdr->Days = get16_le(&pch);
504 hdr->Custom1 = get16_le(&pch);
505 hdr->Custom2 = get16_le(&pch);
506 memcpy(hdr->Info, pch, sizeof(hdr->Info));
507 pch += sizeof(hdr->Info);
508 memcpy(hdr->extend, pch, sizeof(hdr->extend));
509 pch += sizeof(hdr->extend);
510 hdr->Crc = get16_le(&pch);
511 if (sum != hdr->Crc)
513 lsx_report("DVMS header checksum error, read %u, calculated %u",
514 hdr->Crc, sum);
515 return (SOX_EOF);
517 return (SOX_SUCCESS);
520 /* ---------------------------------------------------------------------- */
523 * note! file must be seekable
525 static int dvms_write_header(sox_format_t * ft, struct dvms_header *hdr)
527 unsigned char hdrbuf[DVMS_HEADER_LEN];
528 unsigned char *pch = hdrbuf;
529 unsigned char *pchs = hdrbuf;
530 int i;
531 unsigned sum;
533 memcpy(pch, hdr->Filename, sizeof(hdr->Filename));
534 pch += sizeof(hdr->Filename);
535 put16_le(&pch, hdr->Id);
536 put16_le(&pch, hdr->State);
537 put32_le(&pch, (unsigned)hdr->Unixtime);
538 put16_le(&pch, hdr->Usender);
539 put16_le(&pch, hdr->Ureceiver);
540 put32_le(&pch, (unsigned) hdr->Length);
541 put16_le(&pch, hdr->Srate);
542 put16_le(&pch, hdr->Days);
543 put16_le(&pch, hdr->Custom1);
544 put16_le(&pch, hdr->Custom2);
545 memcpy(pch, hdr->Info, sizeof(hdr->Info));
546 pch += sizeof(hdr->Info);
547 memcpy(pch, hdr->extend, sizeof(hdr->extend));
548 pch += sizeof(hdr->extend);
549 for(i = sizeof(hdrbuf), sum = 0; i > /*2*/3; i--) /* Deti bug */
550 sum += *pchs++;
551 hdr->Crc = sum;
552 put16_le(&pch, hdr->Crc);
553 if (lsx_seeki(ft, (off_t)0, SEEK_SET) < 0)
555 lsx_report("seek failed\n: %s",strerror(errno));
556 return (SOX_EOF);
558 if (lsx_writebuf(ft, hdrbuf, sizeof(hdrbuf)) != sizeof(hdrbuf))
560 lsx_report("%s",strerror(errno));
561 return (SOX_EOF);
563 return (SOX_SUCCESS);
566 /* ---------------------------------------------------------------------- */
568 static void make_dvms_hdr(sox_format_t * ft, struct dvms_header *hdr)
570 priv_t *p = (priv_t *) ft->priv;
571 size_t len;
572 char * comment = lsx_cat_comments(ft->oob.comments);
574 memset(hdr->Filename, 0, sizeof(hdr->Filename));
575 len = strlen(ft->filename);
576 if (len >= sizeof(hdr->Filename))
577 len = sizeof(hdr->Filename)-1;
578 memcpy(hdr->Filename, ft->filename, len);
579 hdr->Id = hdr->State = 0;
580 hdr->Unixtime = sox_globals.repeatable? 0 : time(NULL);
581 hdr->Usender = hdr->Ureceiver = 0;
582 hdr->Length = p->bytes_written;
583 hdr->Srate = p->cvsd_rate/100;
584 hdr->Days = hdr->Custom1 = hdr->Custom2 = 0;
585 memset(hdr->Info, 0, sizeof(hdr->Info));
586 len = strlen(comment);
587 if (len >= sizeof(hdr->Info))
588 len = sizeof(hdr->Info)-1;
589 memcpy(hdr->Info, comment, len);
590 memset(hdr->extend, 0, sizeof(hdr->extend));
591 free(comment);
594 /* ---------------------------------------------------------------------- */
596 int lsx_dvmsstartread(sox_format_t * ft)
598 struct dvms_header hdr;
599 int rc;
601 rc = dvms_read_header(ft, &hdr);
602 if (rc){
603 lsx_fail_errno(ft,SOX_EHDR,"unable to read DVMS header");
604 return rc;
607 lsx_debug("DVMS header of source file \"%s\":", ft->filename);
608 lsx_debug(" filename \"%.14s\"", hdr.Filename);
609 lsx_debug(" id 0x%x", hdr.Id);
610 lsx_debug(" state 0x%x", hdr.State);
611 lsx_debug(" time %s", ctime(&hdr.Unixtime)); /* ctime generates lf */
612 lsx_debug(" usender %u", hdr.Usender);
613 lsx_debug(" ureceiver %u", hdr.Ureceiver);
614 lsx_debug(" length %" PRIuPTR, hdr.Length);
615 lsx_debug(" srate %u", hdr.Srate);
616 lsx_debug(" days %u", hdr.Days);
617 lsx_debug(" custom1 %u", hdr.Custom1);
618 lsx_debug(" custom2 %u", hdr.Custom2);
619 lsx_debug(" info \"%.16s\"", hdr.Info);
620 ft->signal.rate = (hdr.Srate < 240) ? 16000 : 32000;
621 lsx_debug("DVMS rate %dbit/s using %gbit/s deviation %g%%",
622 hdr.Srate*100, ft->signal.rate,
623 ((ft->signal.rate - hdr.Srate*100) * 100) / ft->signal.rate);
624 rc = lsx_cvsdstartread(ft);
625 if (rc)
626 return rc;
628 return(SOX_SUCCESS);
631 /* ---------------------------------------------------------------------- */
633 int lsx_dvmsstartwrite(sox_format_t * ft)
635 struct dvms_header hdr;
636 int rc;
638 rc = lsx_cvsdstartwrite(ft);
639 if (rc)
640 return rc;
642 make_dvms_hdr(ft, &hdr);
643 rc = dvms_write_header(ft, &hdr);
644 if (rc){
645 lsx_fail_errno(ft,rc,"cannot write DVMS header");
646 return rc;
649 if (!ft->seekable)
650 lsx_warn("Length in output .DVMS header will wrong since can't seek to fix it");
652 return(SOX_SUCCESS);
655 /* ---------------------------------------------------------------------- */
657 int lsx_dvmsstopwrite(sox_format_t * ft)
659 struct dvms_header hdr;
660 int rc;
662 lsx_cvsdstopwrite(ft);
663 if (!ft->seekable)
665 lsx_warn("File not seekable");
666 return (SOX_EOF);
668 if (lsx_seeki(ft, (off_t)0, 0) != 0)
670 lsx_fail_errno(ft,errno,"Can't rewind output file to rewrite DVMS header.");
671 return(SOX_EOF);
673 make_dvms_hdr(ft, &hdr);
674 rc = dvms_write_header(ft, &hdr);
675 if(rc){
676 lsx_fail_errno(ft,rc,"cannot write DVMS header");
677 return rc;
679 return rc;