formats: clarify setting of reverse_bytes
[sox.git] / src / smp.c
blobbf8ac63285e66a1ac1ef1fb7e0d5cbfd6638e2af
1 /* libSoX SampleVision file format handler.
2 * Output is always in little-endian (80x86/VAX) order.
4 * Derived from: libSoX skeleton handler file.
6 * Add: Loop point verbose info. It's a start, anyway.
7 */
9 /*
10 * June 30, 1992
11 * Copyright 1992 Leigh Smith And Sundry Contributors
12 * This source code is freely redistributable and may be used for
13 * any purpose. This copyright notice must be maintained.
14 * Leigh Smith And Sundry Contributors are not responsible for
15 * the consequences of using this software.
18 #include "sox_i.h"
19 #include <string.h>
20 #include <errno.h>
22 #define NAMELEN 30 /* Size of Samplevision name */
23 #define COMMENTLEN 60 /* Size of Samplevision comment, not shared */
24 #define MIDI_UNITY 60 /* MIDI note number to play sample at unity */
25 #define MARKERLEN (size_t)10 /* Size of Marker name */
27 /* The header preceeding the sample data */
28 struct smpheader {
29 char Id[18]; /* File identifier */
30 char version[4]; /* File version */
31 char comments[COMMENTLEN]; /* User comments */
32 char name[NAMELEN + 1]; /* Sample Name, left justified */
34 #define HEADERSIZE (sizeof(struct smpheader) - 1) /* -1 for name's \0 */
36 /* Samplevision loop definition structure */
37 struct loop {
38 uint32_t start; /* Sample count into sample data, not byte count */
39 uint32_t end; /* end point */
40 unsigned char type; /* 0 = loop off, 1 = forward, 2 = forw/back */
41 unsigned short count; /* No of times to loop */
44 /* Samplevision marker definition structure */
45 struct marker {
46 char name[MARKERLEN + 1]; /* Ascii Marker name */
47 uint32_t position; /* Sample Number, not byte number */
50 /* The trailer following the sample data */
51 struct smptrailer {
52 struct loop loops[8]; /* loops */
53 struct marker markers[8]; /* markers */
54 int8_t MIDInote; /* for unity pitch playback */
55 uint32_t rate; /* in hertz */
56 uint32_t SMPTEoffset; /* in subframes - huh? */
57 uint32_t CycleSize; /* sample count in one cycle of the */
58 /* sampled sound -1 if unknown */
61 /* Private data for SMP file */
62 typedef struct {
63 uint64_t NoOfSamps; /* Sample data count in words */
64 uint64_t dataStart;
65 /* comment memory resides in private data because it's small */
66 char comment[COMMENTLEN + NAMELEN + 3];
67 } priv_t;
69 static char const *SVmagic = "SOUND SAMPLE DATA ", *SVvers = "2.1 ";
72 * Read the SampleVision trailer structure.
73 * Returns 1 if everything was read ok, 0 if there was an error.
75 static int readtrailer(sox_format_t * ft, struct smptrailer *trailer)
77 int i;
78 uint16_t trash16;
80 lsx_readw(ft, &trash16); /* read reserved word */
81 for(i = 0; i < 8; i++) { /* read the 8 loops */
82 lsx_readdw(ft, &(trailer->loops[i].start));
83 ft->oob.loops[i].start = trailer->loops[i].start;
84 lsx_readdw(ft, &(trailer->loops[i].end));
85 ft->oob.loops[i].length =
86 trailer->loops[i].end - trailer->loops[i].start;
87 lsx_readb(ft, &(trailer->loops[i].type));
88 ft->oob.loops[i].type = trailer->loops[i].type;
89 lsx_readw(ft, &(trailer->loops[i].count));
90 ft->oob.loops[i].count = trailer->loops[i].count;
92 for(i = 0; i < 8; i++) { /* read the 8 markers */
93 if (lsx_readbuf(ft, trailer->markers[i].name, MARKERLEN) != MARKERLEN)
95 lsx_fail_errno(ft,SOX_EHDR,"EOF in SMP");
96 return(SOX_EOF);
98 trailer->markers[i].name[MARKERLEN] = 0;
99 lsx_readdw(ft, &(trailer->markers[i].position));
101 lsx_readsb(ft, &(trailer->MIDInote));
102 lsx_readdw(ft, &(trailer->rate));
103 lsx_readdw(ft, &(trailer->SMPTEoffset));
104 lsx_readdw(ft, &(trailer->CycleSize));
105 return(SOX_SUCCESS);
109 * set the trailer data - loops and markers, to reasonably benign values
111 static void settrailer(sox_format_t * ft, struct smptrailer *trailer, sox_rate_t rate)
113 int i;
115 for(i = 0; i < 8; i++) { /* copy the 8 loops */
116 if (ft->oob.loops[i].type != 0) {
117 trailer->loops[i].start = ft->oob.loops[i].start > UINT_MAX
118 ? UINT_MAX
119 : ft->oob.loops[i].start;
120 /* to mark it as not set */
121 trailer->loops[i].end = ft->oob.loops[i].start + ft->oob.loops[i].length > UINT_MAX
122 ? UINT_MAX
123 : ft->oob.loops[i].start + ft->oob.loops[i].length;
124 trailer->loops[i].type = ft->oob.loops[i].type;
125 trailer->loops[i].count = ft->oob.loops[i].count;
126 } else {
127 /* set first loop start as FFFFFFFF */
128 trailer->loops[i].start = ~0u;
129 /* to mark it as not set */
130 trailer->loops[i].end = 0;
131 trailer->loops[i].type = 0;
132 trailer->loops[i].count = 0;
135 for(i = 0; i < 8; i++) { /* write the 8 markers */
136 strcpy(trailer->markers[i].name, " ");
137 trailer->markers[i].position = ~0u;
139 trailer->MIDInote = MIDI_UNITY; /* Unity play back */
140 trailer->rate = rate;
141 trailer->SMPTEoffset = 0;
142 trailer->CycleSize = ~0u;
146 * Write the SampleVision trailer structure.
147 * Returns 1 if everything was written ok, 0 if there was an error.
149 static int writetrailer(sox_format_t * ft, struct smptrailer *trailer)
151 int i;
153 lsx_writew(ft, 0); /* write the reserved word */
154 for(i = 0; i < 8; i++) { /* write the 8 loops */
155 lsx_writedw(ft, trailer->loops[i].start);
156 lsx_writedw(ft, trailer->loops[i].end);
157 lsx_writeb(ft, trailer->loops[i].type);
158 lsx_writew(ft, trailer->loops[i].count);
160 for(i = 0; i < 8; i++) { /* write the 8 markers */
161 if (lsx_writes(ft, trailer->markers[i].name) == SOX_EOF)
163 lsx_fail_errno(ft,SOX_EHDR,"EOF in SMP");
164 return(SOX_EOF);
166 lsx_writedw(ft, trailer->markers[i].position);
168 lsx_writeb(ft, (uint8_t)(trailer->MIDInote));
169 lsx_writedw(ft, trailer->rate);
170 lsx_writedw(ft, trailer->SMPTEoffset);
171 lsx_writedw(ft, trailer->CycleSize);
172 return(SOX_SUCCESS);
175 static int sox_smpseek(sox_format_t * ft, uint64_t offset)
177 uint64_t new_offset;
178 size_t channel_block, alignment;
179 priv_t * smp = (priv_t *) ft->priv;
181 new_offset = offset * (ft->encoding.bits_per_sample >> 3);
182 /* Make sure request aligns to a channel block (ie left+right) */
183 channel_block = ft->signal.channels * (ft->encoding.bits_per_sample >> 3);
184 alignment = new_offset % channel_block;
185 /* Most common mistaken is to compute something like
186 * "skip everthing upto and including this sample" so
187 * advance to next sample block in this case.
189 if (alignment != 0)
190 new_offset += (channel_block - alignment);
191 new_offset += smp->dataStart;
193 ft->sox_errno = lsx_seeki(ft, (off_t)new_offset, SEEK_SET);
195 if( ft->sox_errno == SOX_SUCCESS )
196 smp->NoOfSamps = ft->signal.length - (new_offset / (ft->encoding.bits_per_sample >> 3));
198 return(ft->sox_errno);
201 * Do anything required before you start reading samples.
202 * Read file header.
203 * Find out sampling rate,
204 * size and encoding of samples,
205 * mono/stereo/quad.
207 static int sox_smpstartread(sox_format_t * ft)
209 priv_t * smp = (priv_t *) ft->priv;
210 int namelen, commentlen;
211 off_t samplestart;
212 size_t i;
213 unsigned dw;
214 struct smpheader header;
215 struct smptrailer trailer;
217 /* If you need to seek around the input file. */
218 if (! ft->seekable)
220 lsx_fail_errno(ft,SOX_EOF,"SMP input file must be a file, not a pipe");
221 return(SOX_EOF);
224 /* Read SampleVision header */
225 if (lsx_readbuf(ft, &header, HEADERSIZE) != HEADERSIZE)
227 lsx_fail_errno(ft,SOX_EHDR,"unexpected EOF in SMP header");
228 return(SOX_EOF);
230 if (strncmp(header.Id, SVmagic, (size_t)17) != 0)
232 lsx_fail_errno(ft,SOX_EHDR,"SMP header does not begin with magic word %s", SVmagic);
233 return(SOX_EOF);
235 if (strncmp(header.version, SVvers, (size_t)4) != 0)
237 lsx_fail_errno(ft,SOX_EHDR,"SMP header is not version %s", SVvers);
238 return(SOX_EOF);
241 /* Format the sample name and comments to a single comment */
242 /* string. We decrement the counters till we encounter non */
243 /* padding space chars, so the *lengths* are low by one */
244 for (namelen = NAMELEN-1;
245 namelen >= 0 && header.name[namelen] == ' '; namelen--)
247 for (commentlen = COMMENTLEN-1;
248 commentlen >= 0 && header.comments[commentlen] == ' '; commentlen--)
250 sprintf(smp->comment, "%.*s: %.*s", namelen+1, header.name,
251 commentlen+1, header.comments);
252 sox_append_comments(&ft->oob.comments, smp->comment);
254 /* Extract out the sample size (always intel format) */
255 lsx_readdw(ft, &dw);
256 smp->NoOfSamps = dw;
257 /* mark the start of the sample data */
258 samplestart = lsx_tell(ft);
260 /* seek from the current position (the start of sample data) by */
261 /* NoOfSamps * sizeof(int16_t) */
262 if (lsx_seeki(ft, (off_t)(smp->NoOfSamps * 2), 1) == -1)
264 lsx_fail_errno(ft,errno,"SMP unable to seek to trailer");
265 return(SOX_EOF);
267 if (readtrailer(ft, &trailer))
269 lsx_fail_errno(ft,SOX_EHDR,"unexpected EOF in SMP trailer");
270 return(SOX_EOF);
273 /* seek back to the beginning of the data */
274 if (lsx_seeki(ft, (off_t)samplestart, 0) == -1)
276 lsx_fail_errno(ft,errno,"SMP unable to seek back to start of sample data");
277 return(SOX_EOF);
280 ft->signal.rate = (int) trailer.rate;
281 ft->encoding.bits_per_sample = 16;
282 ft->encoding.encoding = SOX_ENCODING_SIGN2;
283 ft->signal.channels = 1;
284 smp->dataStart = samplestart;
285 ft->signal.length = smp->NoOfSamps;
287 lsx_report("SampleVision trailer:");
288 for(i = 0; i < 8; i++) if (1 || trailer.loops[i].count) {
289 lsx_report("Loop %lu: start: %6d", (unsigned long)i, trailer.loops[i].start);
290 lsx_report(" end: %6d", trailer.loops[i].end);
291 lsx_report(" count: %6d", trailer.loops[i].count);
292 switch(trailer.loops[i].type) {
293 case 0: lsx_report("type: off"); break;
294 case 1: lsx_report("type: forward"); break;
295 case 2: lsx_report("type: forward/backward"); break;
298 lsx_report("MIDI Note number: %d", trailer.MIDInote);
300 ft->oob.instr.nloops = 0;
301 for(i = 0; i < 8; i++)
302 if (trailer.loops[i].type)
303 ft->oob.instr.nloops++;
304 for(i = 0; i < ft->oob.instr.nloops; i++) {
305 ft->oob.loops[i].type = trailer.loops[i].type;
306 ft->oob.loops[i].count = trailer.loops[i].count;
307 ft->oob.loops[i].start = trailer.loops[i].start;
308 ft->oob.loops[i].length = trailer.loops[i].end
309 - trailer.loops[i].start;
311 ft->oob.instr.MIDIlow = ft->oob.instr.MIDIhi =
312 ft->oob.instr.MIDInote = trailer.MIDInote;
313 if (ft->oob.instr.nloops > 0)
314 ft->oob.instr.loopmode = SOX_LOOP_8;
315 else
316 ft->oob.instr.loopmode = SOX_LOOP_NONE;
318 return(SOX_SUCCESS);
322 * Read up to len samples from file.
323 * Convert to signed longs.
324 * Place in buf[].
325 * Return number of samples read.
327 static size_t sox_smpread(sox_format_t * ft, sox_sample_t *buf, size_t len)
329 priv_t * smp = (priv_t *) ft->priv;
330 unsigned short datum;
331 size_t done = 0;
333 for(; done < len && smp->NoOfSamps; done++, smp->NoOfSamps--) {
334 lsx_readw(ft, &datum);
335 /* scale signed up to long's range */
336 *buf++ = SOX_SIGNED_16BIT_TO_SAMPLE(datum,);
338 return done;
341 static int sox_smpstartwrite(sox_format_t * ft)
343 priv_t * smp = (priv_t *) ft->priv;
344 struct smpheader header;
345 char * comment = lsx_cat_comments(ft->oob.comments);
347 /* If you have to seek around the output file */
348 if (! ft->seekable)
350 lsx_fail_errno(ft,SOX_EOF,"Output .smp file must be a file, not a pipe");
351 return(SOX_EOF);
354 memcpy(header.Id, SVmagic, sizeof(header.Id));
355 memcpy(header.version, SVvers, sizeof(header.version));
356 sprintf(header.comments, "%-*s", COMMENTLEN - 1, "Converted using Sox.");
357 sprintf(header.name, "%-*.*s", NAMELEN, NAMELEN, comment);
358 free(comment);
360 /* Write file header */
361 if(lsx_writebuf(ft, &header, HEADERSIZE) != HEADERSIZE)
363 lsx_fail_errno(ft,errno,"SMP: Can't write header completely");
364 return(SOX_EOF);
366 lsx_writedw(ft, 0); /* write as zero length for now, update later */
367 smp->NoOfSamps = 0;
369 return(SOX_SUCCESS);
372 static size_t sox_smpwrite(sox_format_t * ft, const sox_sample_t *buf, size_t len)
374 priv_t * smp = (priv_t *) ft->priv;
375 int datum;
376 size_t done = 0;
378 while(done < len) {
379 SOX_SAMPLE_LOCALS;
380 datum = (int) SOX_SAMPLE_TO_SIGNED_16BIT(*buf++, ft->clips);
381 lsx_writew(ft, (uint16_t)datum);
382 smp->NoOfSamps++;
383 done++;
386 return(done);
389 static int sox_smpstopwrite(sox_format_t * ft)
391 priv_t * smp = (priv_t *) ft->priv;
392 struct smptrailer trailer;
394 /* Assign the trailer data */
395 settrailer(ft, &trailer, ft->signal.rate);
396 writetrailer(ft, &trailer);
397 if (lsx_seeki(ft, (off_t)112, 0) == -1)
399 lsx_fail_errno(ft,errno,"SMP unable to seek back to save size");
400 return(SOX_EOF);
402 lsx_writedw(ft, smp->NoOfSamps > UINT_MAX ? UINT_MAX : (unsigned)smp->NoOfSamps);
404 return(SOX_SUCCESS);
407 LSX_FORMAT_HANDLER(smp)
409 static char const * const names[] = {"smp", NULL};
410 static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 16, 0, 0};
411 static sox_format_handler_t handler = {SOX_LIB_VERSION_CODE,
412 "Turtle Beach SampleVision", names, SOX_FILE_LIT_END | SOX_FILE_MONO,
413 sox_smpstartread, sox_smpread, NULL,
414 sox_smpstartwrite, sox_smpwrite, sox_smpstopwrite,
415 sox_smpseek, write_encodings, NULL, sizeof(priv_t)
417 return &handler;