1 /* Sound_files.cpp
2 *
3 * Copyright (C) 1992-2018 Paul Boersma & David Weenink
4 *
5 * This code is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or (at
8 * your option) any later version.
9 *
10 * This code is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this work. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20 * pb 2002/07/16 GPL
21 * pb 2003/09/12 MelderFile_checkSoundFile
22 * pb 2004/10/17 test for NULL file pointer when closing in Sound_read(2)FromSoundFile
23 * pb 2005/06/17 Mac headers
24 * pb 2006/01/05 movies for Mac
25 * pb 2006/05/29 QuickTime inclusion made optional
26 * pb 2006/10/28 erased MacOS 9 stuff
27 * pb 2006/12/30 stereo
28 * pb 2006/01/01 more stereo
29 * pb 2007/01/28 removed a warning
30 * pb 2007/01/28 made readFromMovieFile compatible with stereo
31 * pb 2007/03/17 resistant against conflicting declarations of Collection
32 * pb 2007/05/08 removed warning about stereo sounds
33 * pb 2007/10/05 removed FSSpec
34 * pb 2007/10/05 made Sound_readFromMacSoundFile compatible with sampling frequencies between 32768 and 65535 Hz
35 * pb 2008/01/19 double
36 * pb 2009/09/21 made stereo movies readable
37 * pb 2010/12/27 support for multiple channels (i.e. more than two)
38 * pb 2011/06/07 C++
39 * pb 2012/04/29 removed Sound Manager stuff
40 * pb 2012/04/29 removed QuickTime
41 */
42
43 /*
44 static void Sound_ulawDecode (Sound me) {
45 double mu = 100, lnu1 = log (1 + mu);
46 for (integer i = 1; i <= my nx; i ++) {
47 double zabs = (exp (fabs (my z [1] [i]) * lnu1) - 1.0) / mu;
48 my z [1] [i] = my z [1] [i] < 0 ? -zabs : zabs;
49 }
50 }
51 static void Sound_alawDecode (Sound me) {
52 double a = 87.6, lna1 = 1.0 + log (a);
53 for (integer i = 1; i <= my nx; i ++) {
54 double zabs = fabs (my z [1] [i]);
55 if (zabs <= 1.0 / lna1) {
56 my z [1] [i] *= lna1 / a;
57 } else {
58 double t = exp (lna1 * zabs - 1.0) / a;
59 my z [1] [i] = my z [1] [i] > 0 ? t : - t;
60 }
61 }
62 }
63 */
64
65 #include <time.h>
66 #include "Sound.h"
67
Sound_readFromSoundFile(MelderFile file)68 autoSound Sound_readFromSoundFile (MelderFile file) {
69 try {
70 autoMelderFile mfile = MelderFile_open (file);
71 int encoding;
72 double sampleRate;
73 integer startOfData, numberOfSamples, numberOfChannels;
74 int fileType = MelderFile_checkSoundFile (file, & numberOfChannels, & encoding, & sampleRate, & startOfData, & numberOfSamples);
75 if (fileType == 0)
76 Melder_throw (U"Not an audio file.");
77 if (fseek (file -> filePointer, startOfData, SEEK_SET) == EOF) // start from beginning of Data Chunk
78 Melder_throw (U"No data in audio file.");
79 if (numberOfSamples < 1)
80 Melder_throw (U"Audio file contains 0 samples.");
81 autoSound me = Sound_createSimple (numberOfChannels, numberOfSamples / sampleRate, sampleRate);
82 Melder_assert (my z.ncol == numberOfSamples);
83 if (encoding == Melder_SHORTEN || encoding == Melder_POLYPHONE)
84 Melder_throw (U"Cannot unshorten. Write to paul.boersma@uva.nl for more information.");
85 Melder_readAudioToFloat (file -> filePointer, encoding, my z.get());
86 mfile.close ();
87 return me;
88 } catch (MelderError) {
89 Melder_throw (U"Sound not read from sound file ", file, U".");
90 }
91 }
92
Sound_readFromSesamFile(MelderFile file)93 autoSound Sound_readFromSesamFile (MelderFile file) {
94 try {
95 autofile f = Melder_fopen (file, "rb");
96 int32 header [1 + 128];
97 for (integer i = 1; i <= 128; i ++)
98 header [i] = bingeti32LE (f);
99 /*
100 * Try SESAM header.
101 */
102 double samplingFrequency = header [126]; // converting up (from 32 to 54 bits)
103 int32 numberOfSamples = header [127];
104 if (samplingFrequency == 0.0 || numberOfSamples == 0) {
105 /*
106 * Try LVS header.
107 */
108 samplingFrequency = header [62];
109 numberOfSamples = (header [6] << 8) - header [68];
110 }
111 if (numberOfSamples < 1 || numberOfSamples > 1000000000 || samplingFrequency < 10.0 || samplingFrequency > 100000000.0)
112 Melder_throw (U"Not a correct SESAM or LVS file.");
113 autoSound me = Sound_createSimple (1, numberOfSamples / samplingFrequency, samplingFrequency);
114 for (int32 i = 1; i <= numberOfSamples; i ++) {
115 my z [1] [i] = (double) bingeti16LE (f) * (1.0 / 2048); // 12 bits
116 }
117 f.close (file);
118 return me;
119 } catch (MelderError) {
120 Melder_throw (U"Sound not read from Sesam file ", file, U".");
121 }
122 }
123
Sound_readFromBellLabsFile(MelderFile file)124 autoSound Sound_readFromBellLabsFile (MelderFile file) {
125 try {
126 autofile f = Melder_fopen (file, "rb");
127
128 /*
129 * Check identity of file: first line is "SIG", second line contains a number.
130 */
131 char tag [200];
132 if (fread (tag, 1, 16, f) < 16 || ! strnequ (tag, "SIG\n", 4))
133 Melder_throw (U"Not a Bell-Labs sound file.");
134 char *endOfTag = strchr (tag + 4, '\n');
135 if (! endOfTag)
136 Melder_throw (U"Second line missing or too long.");
137 integer tagLength = (endOfTag - tag) + 1; // probably 12
138 int64 headerLength = atol (tag + 4);
139 if (headerLength <= 0)
140 Melder_throw (U"Wrong header-length info.");
141
142 /*
143 * Read data from header.
144 * Use defaults if necessary.
145 */
146 autostring8 lines (headerLength);
147 if ((int64) fread (lines.get(), 1, (size_t) headerLength, f) < headerLength)
148 Melder_throw (U"Header too short.");
149 integer numberOfSamples = 0;
150 char *psamples = & lines [-1];
151 while (!! (psamples = strstr (psamples + 1, "samples "))) // take last occurrence
152 numberOfSamples = atol (psamples + 8);
153 if (numberOfSamples < 1) {
154 /* Use file length. */
155 fseek (f, 0, SEEK_END); /* Position file pointer at end of file. */
156 numberOfSamples = (ftell (f) - tagLength - headerLength) / 2;
157 }
158 if (numberOfSamples < 1)
159 Melder_throw (U"No samples found.");
160 double samplingFrequency = 0.0;
161 char *pfrequency = & lines [-1];
162 while (!! (pfrequency = strstr (pfrequency + 1, "frequency "))) // take last occurrence
163 samplingFrequency = atof (pfrequency + 10);
164 if (samplingFrequency <= 0.0)
165 samplingFrequency = 16000.0;
166
167 /*
168 * Create sound.
169 */
170 autoSound me = Sound_createSimple (1, numberOfSamples / samplingFrequency, samplingFrequency);
171
172 /*
173 * Read samples.
174 */
175 fseek (f, tagLength + headerLength, SEEK_SET);
176 for (integer i = 1; i <= numberOfSamples; i ++)
177 my z [1] [i] = (double) bingeti16 (f) * (1.0 / 32768); // 16-bits big-endian
178
179 f.close (file);
180 return me;
181 } catch (MelderError) {
182 Melder_throw (U"Sound not read from Bell Labs sound file ", file, U".");
183 }
184 }
185
readError()186 static void readError () {
187 Melder_throw (U"Error reading bytes from file.");
188 }
189
Sound_readFromKayFile(MelderFile file)190 autoSound Sound_readFromKayFile (MelderFile file) {
191 try {
192 autofile f = Melder_fopen (file, "rb");
193
194 /* Read header of KAY file: 12 bytes. */
195
196 char data [100];
197 if (fread (data, 1, 12, f) < 12) readError ();
198 if (! strnequ (data, "FORMDS16", 8))
199 Melder_throw (U"Not a KAY DS-16 file.");
200
201 /* HEDR or HDR8 chunk */
202
203 if (fread (data, 1, 4, f) < 4) readError ();
204 if (! strnequ (data, "HEDR", 4) && ! strnequ (data, "HDR8", 4))
205 Melder_throw (U"Missing HEDR or HDR8 chunk. Please report to paul.boersma@uva.nl.");
206 uint32 chunkSize = bingetu32LE (f);
207 if (chunkSize & 1) ++ chunkSize;
208 if (chunkSize != 32 && chunkSize != 44)
209 Melder_throw (U"Unknown chunk size ", chunkSize, U". Please report to paul.boersma@uva.nl.");
210 if (fread (data, 1, 20, f) < 20) readError ();
211 double samplingFrequency = bingetu32LE (f); // converting up (from 32 to 53 bits)
212 uint32 numberOfSamples = bingetu32LE (f);
213 if (samplingFrequency <= 0 || samplingFrequency > 1e7 || numberOfSamples >= 1000000000)
214 Melder_throw (U"Not a correct Kay file.");
215 int16 tmp1 = bingeti16LE (f);
216 int16 tmp2 = bingeti16LE (f);
217 integer numberOfChannels = ( tmp1 == -1 || tmp2 == -1 ? 1 : 2 );
218 if (chunkSize == 44) {
219 int16 tmp3 = bingeti16LE (f);
220 if (tmp3 != -1) numberOfChannels ++;
221 int16 tmp4 = bingeti16LE (f);
222 if (tmp4 != -1) numberOfChannels ++;
223 int16 tmp5 = bingeti16LE (f);
224 if (tmp5 != -1) numberOfChannels ++;
225 int16 tmp6 = bingeti16LE (f);
226 if (tmp6 != -1) numberOfChannels ++;
227 int16 tmp7 = bingeti16LE (f);
228 if (tmp7 != -1) numberOfChannels ++;
229 int16 tmp8 = bingeti16LE (f);
230 if (tmp8 != -1) numberOfChannels ++;
231 }
232
233 /* SD chunk */
234
235 autoSound me = Sound_createSimple (numberOfChannels, numberOfSamples / samplingFrequency, samplingFrequency);
236 for (integer ichan = 1; ichan <= numberOfChannels; ichan ++) {
237 if (fread (data, 1, 4, f) < 4) readError ();
238 while (! strnequ (data, "SD", 2)) {
239 if (feof ((FILE *) f))
240 Melder_throw (U"Missing or unreadable SD chunk. Please report to paul.boersma@uva.nl.");
241 chunkSize = bingetu32LE (f);
242 if (chunkSize & 1)
243 ++ chunkSize;
244 Melder_casual (U"Chunk ",
245 data [0], U" ", data [1], U" ", data [2], U" ", data [3], U" ", chunkSize);
246 fseek (f, chunkSize, SEEK_CUR);
247 if (fread (data, 1, 4, f) < 4)
248 readError ();
249 }
250 chunkSize = bingetu32LE (f);
251 integer residual = chunkSize - numberOfSamples * 2;
252 if (residual < 0)
253 Melder_throw (U"Incomplete SD chunk: attested size ", chunkSize, U" bytes,"
254 U" announced size ", numberOfSamples * 2, U" bytes. Please report to paul.boersma@uva.nl.");
255
256 for (integer i = 1; i <= numberOfSamples; i ++)
257 my z [ichan] [i] = (double) bingeti16LE (f) / 32768.0;
258 fseek (f, residual, SEEK_CUR);
259 }
260 //Melder_casual (ftell (f));
261 f.close (file);
262 return me;
263 } catch (MelderError) {
264 Melder_throw (U"Sound not read from Kay file ", file, U".");
265 }
266 }
267
Sound_readFromRawAlawFile(MelderFile file)268 autoSound Sound_readFromRawAlawFile (MelderFile file) {
269 try {
270 double sampleRate = 8000.0;
271 autofile f = Melder_fopen (file, "rb");
272 fseek (f, 0, SEEK_END);
273 integer numberOfSamples = ftell (f);
274 rewind (f);
275 autoSound me = Sound_createSimple (1, numberOfSamples / sampleRate, sampleRate);
276 Melder_assert (my z.ncol == numberOfSamples);
277 Melder_readAudioToFloat (f, Melder_ALAW, my z.get());
278 f.close (file);
279 return me;
280 } catch (MelderError) {
281 Melder_throw (U"Sound not read from raw A-law file ", file, U".");
282 }
283 }
284
Sound_saveAsAudioFile(Sound me,MelderFile file,int audioFileType,int numberOfBitsPerSamplePoint)285 void Sound_saveAsAudioFile (Sound me, MelderFile file, int audioFileType, int numberOfBitsPerSamplePoint) {
286 try {
287 autoMelderFile mfile = MelderFile_create (file);
288 MelderFile_writeAudioFileHeader (file, audioFileType, Melder_iround_tieDown (1.0 / my dx), my nx, my ny, numberOfBitsPerSamplePoint);
289 MelderFile_writeFloatToAudio (file, my z.get(), Melder_defaultAudioFileEncoding (audioFileType, numberOfBitsPerSamplePoint), true);
290 MelderFile_writeAudioFileTrailer (file, audioFileType, Melder_iround (1.0 / my dx), my nx, my ny, numberOfBitsPerSamplePoint);
291 mfile.close ();
292 } catch (MelderError) {
293 Melder_throw (me, U": not written to 16-bit sound file ", file, U".");
294 }
295 }
296
Sound_saveAsSesamFile(Sound me,MelderFile file)297 void Sound_saveAsSesamFile (Sound me, MelderFile file) {
298 try {
299 autofile f = Melder_fopen (file, "wb");
300 integer header [1 + 128];
301 for (integer i = 1; i <= 128; i ++) header [i] = 0;
302 /* ILS header. */
303 header [6] = ((my nx - 1) >> 8) + 1; // number of disk blocks
304 header [64] = 32149; // ILS magic
305 /* LVS header. */
306 header [62] = Melder_iround_tieDown (1.0 / my dx); // sampling frequency, rounded to n Hz
307 header [63] = -32000; // magic: "sampled signal"
308 header [66] = INT12_MAX; // maximum absolute value: 12 bits
309 header [67] = 2047; // LVS magic
310 header [68] = my nx % 256; // number of samples in last block
311 header [69] = 1; // ?
312 /* Sesam header. */
313 header [126] = Melder_iround_tieDown (1.0 / my dx); // sampling frequency, rounded to n Hz
314 header [127] = my nx; // number of samples
315 for (integer i = 1; i <= 128; i ++)
316 binputi32LE (header [i], f);
317 for (integer i = 1; i <= my nx; i ++)
318 binputi16LE (Melder_iround_tieDown (my z [1] [i] * 2048.0), f);
319 integer tail = 256 - my nx % 256;
320 if (tail == 256)
321 tail = 0;
322 for (integer i = 1; i <= tail; i ++)
323 binputi16LE (0, f); // pad last block with zeroes
324 f.close (file);
325 } catch (MelderError) {
326 Melder_throw (me, U": not written to Sesam file ", file, U".");
327 }
328 }
329
Sound_saveAsKayFile(Sound me,MelderFile file)330 void Sound_saveAsKayFile (Sound me, MelderFile file) {
331 try {
332 Melder_require (my ny <= 8,
333 U"Cannot write more than 8 channels into a Kay sound file.");
334
335 autoMelderFile mfile = MelderFile_create (file);
336
337 /* Form Chunk: contains all other chunks. */
338 fwrite ("FORMDS16", 1, 8, file -> filePointer);
339 binputi32LE (48 + my nx * 2, file -> filePointer); // size of Form Chunk
340 fwrite (my ny > 2 ? "HDR8" : "HEDR", 1, 4, file -> filePointer);
341 binputi32LE (my ny > 2 ? 44 : 32, file -> filePointer);
342
343 char date [100];
344 time_t today = time (nullptr);
345 strcpy (date, ctime (& today));
346 fwrite (date+4, 1, 20, file -> filePointer); // skip weekday
347
348 binputi32LE (Melder_iround_tieDown (1.0 / my dx), file -> filePointer); // sampling frequency
349 binputi32LE (my nx, file -> filePointer); // number of samples
350 int maximumA = 0;
351 for (integer i = 1; i <= my nx; i ++) {
352 integer value = Melder_iround_tieDown (my z [1] [i] * 32768.0);
353 if (value < - maximumA) maximumA = - value;
354 if (value > maximumA) maximumA = value;
355 }
356 binputi16LE (maximumA, file -> filePointer); // absolute maximum window A
357 if (my ny == 1) {
358 binputi16LE (-1, file -> filePointer);
359 } else {
360 for (integer ichannel = 2; ichannel <= my ny; ichannel ++) {
361 int maximum = 0;
362 for (integer i = 1; i <= my nx; i ++) {
363 integer value = Melder_iround_tieDown (my z [ichannel] [i] * 32768.0);
364 if (value < - maximum) maximum = - value;
365 if (value > maximum) maximum = value;
366 }
367 binputi16LE (maximum, file -> filePointer); // absolute maximum window B
368 }
369 if (my ny > 2)
370 for (integer ichannel = my ny + 1; ichannel <= 8; ichannel ++)
371 binputi16LE (-1, file -> filePointer);
372 }
373 fwrite ("SDA_", 1, 4, file -> filePointer);
374 binputi32LE (my nx * 2, file -> filePointer); // chunk size
375 MelderFile_writeFloatToAudio (file,
376 my z.horizontalBand (1, 1), Melder_LINEAR_16_LITTLE_ENDIAN, true);
377 if (my ny > 1) {
378 fwrite ("SD_B", 1, 4, file -> filePointer);
379 binputi32LE (my nx * 2, file -> filePointer); // chunk size
380 MelderFile_writeFloatToAudio (file,
381 my z.horizontalBand (2, 2), Melder_LINEAR_16_LITTLE_ENDIAN, true);
382 }
383 for (integer ichannel = 3; ichannel <= my ny; ichannel ++) {
384 fwrite (Melder_peek32to8 (Melder_cat (U"SD_", ichannel)), 1, 4, file -> filePointer);
385 binputi32LE (my nx * 2, file -> filePointer); // chunk size
386 MelderFile_writeFloatToAudio (file,
387 my z.horizontalBand (ichannel, ichannel), Melder_LINEAR_16_LITTLE_ENDIAN, true);
388 }
389 mfile.close ();
390 } catch (MelderError) {
391 Melder_throw (me, U": not written to Kay sound file ", file, U".");
392 }
393 }
394
Sound_saveAsRawSoundFile(Sound me,MelderFile file,int encoding)395 void Sound_saveAsRawSoundFile (Sound me, MelderFile file, int encoding) {
396 try {
397 autoMelderFile mfile = MelderFile_create (file);
398 MelderFile_writeFloatToAudio (file, my z.get(), encoding, true);
399 mfile.close ();
400 } catch (MelderError) {
401 Melder_throw (me, U": not written to raw sound file ", file, U".");
402 }
403 }
404
405 /* End of file Sound_files.cpp */
406