1 /* LongSound_extensions.c
2  *
3  * Copyright (C) 1993-2017 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.  See the GNU
13  * 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  djmw 20020627 GPL header
21  djmw 20030913 changed 'f' to 'file' as argument in Melder_checkSoundFile
22  djmw 20060206 Set errno = 0:  "An application that needs to examine the value
23  	of errno to determine the error should set it to 0 before a function call,
24  	then inspect it before a subsequent function call."
25  djmw 20061213 MelderFile_truncate also works for MacOS X
26  djmw 20061212 Header unistd.h for MacOS X added.
27  djmw 20070129 Sounds may be multichannel
28  djmw 20071030 MelderFile->wpath to  MelderFile->path
29 */
30 
31 #include "LongSound_extensions.h"
32 
33 #if defined (_WIN32)
34 #include "winport_on.h"
35 #include <windows.h>
36 #include "winport_off.h"
37 #elif defined(linux)
38 #include <unistd.h>
39 #include <sys/types.h>
40 #include <string.h>
41 #elif defined (macintosh)
42 #include <unistd.h>
43 #endif
44 
45 #include "NUM2.h"
46 #include <errno.h>
47 
48 /*
49   Precondition: size (my buffer) >= nbuf
50 */
_LongSound_to_multichannel_buffer(LongSound me,short * buffer,integer nbuf,int nchannels,int ichannel,integer ibuf)51 static void _LongSound_to_multichannel_buffer (LongSound me, short *buffer, integer nbuf, int nchannels, int ichannel, integer ibuf) {
52 	const integer numberOfReads = (my nx - 1) / nbuf + 1;
53 	integer n_to_read = 0;
54 
55 	if (ibuf <= numberOfReads) {
56 		n_to_read = ( ibuf == numberOfReads ? (my nx - 1) % nbuf + 1 : nbuf );
57 		const integer imin = (ibuf - 1) * nbuf + 1;
58 		my invalidateBuffer();
59 		LongSound_readAudioToShort (me, my buffer.asArgumentToFunctionThatExpectsZeroBasedArray(), imin, n_to_read);
60 
61 		for (integer i = 1; i <= n_to_read; i ++)
62 			buffer [nchannels * (i - 1) + ichannel] = my buffer [i];
63 	}
64 	if (ibuf >= numberOfReads)
65 		for (integer i = n_to_read + 1; i <= nbuf; i ++)
66 			buffer [nchannels * (i - 1) + ichannel] = 0;
67 }
68 
LongSounds_writeToStereoAudioFile16(LongSound me,LongSound thee,int audioFileType,MelderFile file)69 void LongSounds_writeToStereoAudioFile16 (LongSound me, LongSound thee, int audioFileType, MelderFile file) {
70 	try {
71 		const integer nbuf = std::min (my nmax, thy nmax);
72 		const integer nx = std::max (my nx, thy nx);
73 		const integer numberOfReads = (nx - 1) / nbuf + 1, numberOfBitsPerSamplePoint = 16;
74 
75 		Melder_require (thy numberOfChannels == my numberOfChannels && my numberOfChannels == 1,
76 			U"The two LongSounds should be mono.");
77 		Melder_require (my sampleRate == thy sampleRate,
78 			U"The two sampling frequencies should be equal.");
79 		/*
80 			Allocate a stereo buffer of size (2 * the smallest)!
81 			WE SUPPOSE THAT SMALL IS LARGE ENOUGH.
82 			Read the same number of samples from both files, despite
83 			potential differences in internal buffer size.
84 		*/
85 
86 		const integer nchannels = 2;
87 		autovector <short> buffer = newvectorzero <short> (nchannels * nbuf);
88 
89 		autoMelderFile f = MelderFile_create (file);
90 		MelderFile_writeAudioFileHeader (file, audioFileType, Melder_ifloor (my sampleRate), nx, nchannels, numberOfBitsPerSamplePoint);
91 
92 		for (integer i = 1; i <= numberOfReads; i ++) {
93 			const integer n_to_write = ( i == numberOfReads ? (nx - 1) % nbuf + 1 : nbuf );
94 			_LongSound_to_multichannel_buffer (me, buffer.asArgumentToFunctionThatExpectsOneBasedArray(), nbuf, nchannels, 1, i);
95 			_LongSound_to_multichannel_buffer (thee, buffer.asArgumentToFunctionThatExpectsOneBasedArray(), nbuf, nchannels, 2, i);
96 			MelderFile_writeShortToAudio (file, nchannels, Melder_defaultAudioFileEncoding (audioFileType,
97                 numberOfBitsPerSamplePoint), & buffer [1], n_to_write);
98 		}
99 		MelderFile_writeAudioFileTrailer (file, audioFileType, Melder_ifloor (my sampleRate), nx, nchannels, numberOfBitsPerSamplePoint);
100 		f.close ();
101 	} catch (MelderError) {
102 		Melder_throw (me, U": no stereo audio file created.");
103 	}
104 }
105 
106 
107 /*
108 	BSD systems provide ftruncate, several others supply chsize, and a few
109 	may provide a (possibly undocumented) fcntl option F_FREESP. Under MS-DOS,
110 	you can sometimes use write(fd, "", 0). However, there is no portable
111 	solution, nor a way to delete blocks at the beginning.
112 */
MelderFile_truncate(MelderFile me,integer size)113 static void MelderFile_truncate (MelderFile me, integer size) {
114 #if defined (_WIN32)
115 	HANDLE hFile;
116 	DWORD fdwAccess = GENERIC_READ | GENERIC_WRITE, fPos;
117 	DWORD fdwShareMode = 0;   // file cannot be shared
118 	LPSECURITY_ATTRIBUTES lpsa = nullptr;
119 	DWORD fdwCreate = OPEN_EXISTING;
120 	LARGE_INTEGER fileSize;
121 
122 	MelderFile_close (me);
123 
124 	hFile = CreateFileW (Melder_peek32toW_fileSystem (my path),
125 			fdwAccess, fdwShareMode, lpsa, fdwCreate, FILE_ATTRIBUTE_NORMAL, nullptr);
126 
127 	Melder_require (hFile != INVALID_HANDLE_VALUE,
128 		U"Cannot open file ", me, U".");
129 
130 	fileSize.LowPart = size; // Set current file pointer to position 'size'
131 	fileSize.HighPart = 0; // Limit the file size to 2^32 - 2 bytes
132 	fPos = SetFilePointer (hFile, fileSize.LowPart, & fileSize.HighPart, FILE_BEGIN);
133 	Melder_require (fPos != 0xFFFF'FFFF,
134 		U"Can't set the position at size ", size, U" for file ", me, U".");
135 
136 	// Limit the file size as the current position of the file pointer.
137 
138 	SetEndOfFile (hFile);
139 	CloseHandle (hFile);
140 #elif defined (linux) || defined (macintosh)
141 	MelderFile_close (me);
142 	const int succes = truncate (Melder_peek32to8 (my path), size);
143 	Melder_require (succes == 0,
144 		U"Truncating failed for file ", me, U" (", Melder_peek8to32 (strerror (errno)), U").");
145 #else
146 	Melder_throw (U"Don't know what to do.");
147 #endif
148 }
149 
writePartToOpenFile16(LongSound me,int audioFileType,integer imin,integer n,MelderFile file)150 static void writePartToOpenFile16 (LongSound me, int audioFileType, integer imin, integer n, MelderFile file) {
151 	const integer numberOfBuffers = (n - 1) / my nmax + 1, numberOfBitsPerSamplePoint = 16;
152 	const integer numberOfSamplesInLastBuffer = (n - 1) % my nmax + 1;
153 	integer offset = imin;
154 	if (file -> filePointer) {
155 		for (integer ibuffer = 1; ibuffer <= numberOfBuffers; ibuffer ++) {
156 			const integer numberOfSamplesToCopy = ( ibuffer < numberOfBuffers ? my nmax : numberOfSamplesInLastBuffer );
157 			my invalidateBuffer();
158 			LongSound_readAudioToShort (me, my buffer.asArgumentToFunctionThatExpectsZeroBasedArray(), offset, numberOfSamplesToCopy);
159 			offset += numberOfSamplesToCopy;
160 			MelderFile_writeShortToAudio (file, my numberOfChannels, Melder_defaultAudioFileEncoding (audioFileType, numberOfBitsPerSamplePoint), my buffer.asArgumentToFunctionThatExpectsZeroBasedArray(), numberOfSamplesToCopy);
161 		}
162 	}
163 }
164 
LongSounds_appendToExistingSoundFile(OrderedOf<structSampled> * me,MelderFile file)165 void LongSounds_appendToExistingSoundFile (OrderedOf<structSampled>* me, MelderFile file) {
166 	integer pre_append_endpos = 0, numberOfBitsPerSamplePoint = 16;
167 	try {
168 		Melder_require (my size > 0,
169 			U"No Sound or LongSound objects to append.");
170 		/*
171 			We have to open with "r+" mode because this will position the stream
172 			at the beginning of the file. The "a" mode does not allow us to
173 			seek before the end-of-file.
174 
175 			For Linux: If the file is already opened (e.g. by a LongSound) object we
176 			should deny access!
177 			Under Windows deny access is default?!
178 		*/
179 
180 		autofile f = Melder_fopen (file, "r+b");
181 		file -> filePointer = f; // essential !!
182 		double sampleRate_d;
183 		integer startOfData, numberOfSamples, numberOfChannels;
184 		int encoding;
185 		const int audioFileType = MelderFile_checkSoundFile (file, & numberOfChannels, & encoding, & sampleRate_d, & startOfData, & numberOfSamples);
186 		Melder_require (audioFileType > 0,
187 			U"Not a sound file.");
188 		/*
189 			Check whether all the sampling frequencies and channels match.
190 		*/
191 		integer sampleRate = Melder_ifloor (sampleRate_d);
192 		for (integer i = 1; i <= my size; i ++) {
193 			bool sampleRatesMatch, numbersOfChannelsMatch;
194 			const Sampled data = my at [i];
195 			if (data -> classInfo == classSound) {
196 				Sound sound = (Sound) data;
197 				sampleRatesMatch = Melder_iround (1.0 / sound -> dx) == sampleRate;
198 				numbersOfChannelsMatch = sound -> ny == numberOfChannels;
199 				numberOfSamples += sound -> nx;
200 			} else {
201 				LongSound longSound = (LongSound) data;
202 				sampleRatesMatch = longSound -> sampleRate == sampleRate;
203 				numbersOfChannelsMatch = longSound -> numberOfChannels == numberOfChannels;
204 				numberOfSamples += longSound -> nx;
205 			}
206 			Melder_require (sampleRatesMatch,
207 				U"Sampling frequencies should match.");
208 			Melder_require (numbersOfChannelsMatch,
209 				U"The number of channels should match.");
210 		}
211 		/*
212 			Search the end of the file, count the number of bytes and append.
213 		*/
214 		MelderFile_seek (file, 0, SEEK_END);
215 		pre_append_endpos = MelderFile_tell (file);
216 
217 		errno = 0;
218 		for (integer i = 1; i <= my size; i ++) {
219 			const Sampled data = my at [i];
220 			if (data -> classInfo == classSound) {
221 				Sound sound = (Sound) data;
222 				MelderFile_writeFloatToAudio (file, sound -> z.get(),
223 						Melder_defaultAudioFileEncoding (audioFileType, numberOfBitsPerSamplePoint), true);
224 			} else {
225 				LongSound longSound = (LongSound) data;
226 				writePartToOpenFile16 (longSound, audioFileType, 1, longSound -> nx, file);
227 			}
228 			Melder_require (errno == 0,
229 				U"Error during writing.");
230 		}
231 		/*
232 			Update header
233 		*/
234 		MelderFile_rewind (file);
235 		MelderFile_writeAudioFileHeader (file, audioFileType, sampleRate, numberOfSamples, numberOfChannels, numberOfBitsPerSamplePoint);
236 		MelderFile_writeAudioFileTrailer (file, audioFileType, sampleRate, numberOfSamples, numberOfChannels, numberOfBitsPerSamplePoint);
237 		f.close (file);
238 		return;
239 	} catch (MelderError) {
240 		if (errno != 0 && pre_append_endpos > 0) {
241 			// Restore file at original size
242 			int error = errno;
243 			MelderFile_truncate (file, pre_append_endpos);
244 			Melder_throw (U"File ", MelderFile_messageName (file), U" restored to original size (", Melder_peek8to32 (strerror (error)), U").");
245 		} throw;
246 	}
247 }
248 
249 /* End of file LongSound_extensions.cpp */
250