1 // b3WriteWavFile is copied from Stk::FileWvOut/FileWrite
2 // See also https://github.com/thestk/stk
3 // by Perry R. Cook and Gary P. Scavone, 1995--2014.
4 
5 #include "b3WriteWavFile.h"
6 #include "Bullet3Common/b3AlignedObjectArray.h"
7 #include "b3SwapUtils.h"
8 
9 #define B3_FLOAT32 32
10 #define B3_FLOAT64 64
11 
12 // WAV header structure. See
13 // http://www-mmsp.ece.mcgill.ca/documents/audioformats/WAVE/Docs/rfc2361.txt
14 // for information regarding format codes.
15 struct b3WaveHeader
16 {
17 	char riff[4];   // "RIFF"
18 	int fileSize;   // in bytes
19 	char wave[4];   // "WAVE"
20 	char fmt[4];    // "fmt "
21 	int chunkSize;  // in bytes (16 for PCM)
22 	union {
23 		signed short formatCode;  // 1=PCM, 2=ADPCM, 3=IEEE float, 6=A-Law, 7=Mu-Law
24 		unsigned short uformatCode;
25 	};
26 	signed short nChannels;  // 1=mono, 2=stereo
27 	int sampleRate;
28 	int bytesPerSecond;
29 	signed short bytesPerSample;  // 2=16-bit mono, 4=16-bit stereo
30 	signed short bitsPerSample;
31 	signed short cbSize;     // size of extension
32 	signed short validBits;  // valid bits per sample
33 	int channelMask;         // speaker position mask
34 	char subformat[16];      // format code and GUID
35 	char fact[4];            // "fact"
36 	int factSize;            // fact chunk size
37 	int frames;              // sample frames
38 };
39 
40 struct b3WriteWavFileInternalData
41 {
42 	FILE *m_file;
43 	int m_numChannels;
44 	int m_sampleRate;
45 	int m_dataType;  // single precision 32bit float, 64bit double
46 	bool m_byteswap;
47 	int m_frameCounter;
48 	int m_bufferIndex;
49 	int m_bufferSize;
50 	bool m_clipped;
51 	bool m_isMachineLittleEndian;
52 
53 	b3AlignedObjectArray<float> m_floatBuffer;
54 	b3AlignedObjectArray<double> m_doubleBuffer;
55 
b3WriteWavFileInternalDatab3WriteWavFileInternalData56 	b3WriteWavFileInternalData()
57 		: m_file(0),
58 		  m_numChannels(0),
59 		  m_dataType(B3_FLOAT32),
60 		  m_byteswap(false),
61 		  m_frameCounter(0),
62 		  m_bufferIndex(0),
63 		  m_bufferSize(1024),
64 		  m_clipped(false)
65 	{
66 		m_floatBuffer.reserve(m_bufferSize);
67 		m_doubleBuffer.reserve(m_bufferSize);
68 		m_isMachineLittleEndian = b3MachineIsLittleEndian();
69 	}
70 };
71 
b3WriteWavFile()72 b3WriteWavFile::b3WriteWavFile()
73 {
74 	m_data = new b3WriteWavFileInternalData();
75 }
76 
~b3WriteWavFile()77 b3WriteWavFile::~b3WriteWavFile()
78 {
79 	closeWavFile();
80 	delete m_data;
81 }
82 
setWavFile(std::string fileName,int sampleRate,int numChannels,bool useDoublePrecision)83 bool b3WriteWavFile::setWavFile(std::string fileName, int sampleRate, int numChannels, bool useDoublePrecision)
84 {
85 	m_data->m_numChannels = numChannels;
86 	m_data->m_sampleRate = sampleRate;
87 	if (useDoublePrecision)
88 	{
89 		m_data->m_dataType = B3_FLOAT64;
90 	}
91 	else
92 	{
93 		m_data->m_dataType = B3_FLOAT32;
94 	}
95 
96 	if (fileName.find(".wav") == std::string::npos)
97 		fileName += ".wav";
98 
99 	m_data->m_file = fopen(fileName.c_str(), "wb");
100 	if (!m_data->m_file)
101 	{
102 		return false;
103 	}
104 
105 	struct b3WaveHeader hdr = {{'R', 'I', 'F', 'F'}, 44, {'W', 'A', 'V', 'E'}, {'f', 'm', 't', ' '}, 16, 1, 1, sampleRate, 0, 2, 16, 0, 0, 0, {'\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x10', '\x00', '\x80', '\x00', '\x00', '\xAA', '\x00', '\x38', '\x9B', '\x71'}, {'f', 'a', 'c', 't'}, 4, 0};
106 	hdr.nChannels = (signed short)m_data->m_numChannels;
107 
108 	if (m_data->m_dataType == B3_FLOAT32)
109 	{
110 		hdr.formatCode = 3;
111 		hdr.bitsPerSample = 32;
112 	}
113 	else if (m_data->m_dataType == B3_FLOAT64)
114 	{
115 		hdr.formatCode = 3;
116 		hdr.bitsPerSample = 64;
117 	}
118 
119 	hdr.bytesPerSample = (signed short)(m_data->m_numChannels * hdr.bitsPerSample / 8);
120 	hdr.bytesPerSecond = (int)(hdr.sampleRate * hdr.bytesPerSample);
121 
122 	unsigned int bytesToWrite = 36;
123 	if (m_data->m_numChannels > 2 || hdr.bitsPerSample > 16)
124 	{  // use extensible format
125 		bytesToWrite = 72;
126 		hdr.chunkSize += 24;
127 		hdr.uformatCode = 0xFFFE;
128 		hdr.cbSize = 22;
129 		hdr.validBits = hdr.bitsPerSample;
130 		signed short *subFormat = (signed short *)&hdr.subformat[0];
131 		if (m_data->m_dataType == B3_FLOAT32 || m_data->m_dataType == B3_FLOAT64)
132 			*subFormat = 3;
133 		else
134 			*subFormat = 1;
135 	}
136 
137 	m_data->m_byteswap = false;
138 	if (!m_data->m_isMachineLittleEndian)
139 	{
140 		m_data->m_byteswap = true;
141 		b3Swap32((unsigned char *)&hdr.chunkSize);
142 		b3Swap16((unsigned char *)&hdr.formatCode);
143 		b3Swap16((unsigned char *)&hdr.nChannels);
144 		b3Swap32((unsigned char *)&hdr.sampleRate);
145 		b3Swap32((unsigned char *)&hdr.bytesPerSecond);
146 		b3Swap16((unsigned char *)&hdr.bytesPerSample);
147 		b3Swap16((unsigned char *)&hdr.bitsPerSample);
148 		b3Swap16((unsigned char *)&hdr.cbSize);
149 		b3Swap16((unsigned char *)&hdr.validBits);
150 		b3Swap16((unsigned char *)&hdr.subformat[0]);
151 		b3Swap32((unsigned char *)&hdr.factSize);
152 	}
153 
154 	char data[4] = {'d', 'a', 't', 'a'};
155 	int dataSize = 0;
156 	if (fwrite(&hdr, 1, bytesToWrite, m_data->m_file) != bytesToWrite)
157 		return false;
158 	if (fwrite(&data, 4, 1, m_data->m_file) != 1)
159 		return false;
160 	if (fwrite(&dataSize, 4, 1, m_data->m_file) != 1)
161 		return false;
162 
163 	return true;
164 }
165 
closeWavFile()166 void b3WriteWavFile::closeWavFile()
167 {
168 	if (m_data->m_file == 0)
169 		return;
170 
171 	flushData(1);
172 
173 	int bytesPerSample = 1;
174 	if (m_data->m_dataType == B3_FLOAT32)
175 		bytesPerSample = 4;
176 	else if (m_data->m_dataType == B3_FLOAT64)
177 		bytesPerSample = 8;
178 
179 	bool useExtensible = false;
180 	int dataLocation = 40;
181 	if (bytesPerSample > 2 || m_data->m_numChannels > 2)
182 	{
183 		useExtensible = true;
184 		dataLocation = 76;
185 	}
186 
187 	int bytes = (int)(m_data->m_frameCounter * m_data->m_numChannels * bytesPerSample);
188 	if (bytes % 2)
189 	{  // pad extra byte if odd
190 		signed char sample = 0;
191 		fwrite(&sample, 1, 1, m_data->m_file);
192 	}
193 #ifndef __LITTLE_ENDIAN__
194 	b3Swap32((unsigned char *)&bytes);
195 #endif
196 	fseek(m_data->m_file, dataLocation, SEEK_SET);  // jump to data length
197 	fwrite(&bytes, 4, 1, m_data->m_file);
198 
199 	bytes = (int)(m_data->m_frameCounter * m_data->m_numChannels * bytesPerSample + 44);
200 	if (useExtensible) bytes += 36;
201 #ifndef __LITTLE_ENDIAN__
202 	b3Swap32((unsigned char *)&bytes);
203 #endif
204 	fseek(m_data->m_file, 4, SEEK_SET);  // jump to file size
205 	fwrite(&bytes, 4, 1, m_data->m_file);
206 
207 	if (useExtensible)
208 	{  // fill in the "fact" chunk frames value
209 		bytes = (int)m_data->m_frameCounter;
210 #ifndef __LITTLE_ENDIAN__
211 		b3Swap32((unsigned char *)&bytes);
212 #endif
213 		fseek(m_data->m_file, 68, SEEK_SET);
214 		fwrite(&bytes, 4, 1, m_data->m_file);
215 	}
216 
217 	fclose(m_data->m_file);
218 	m_data->m_file = 0;
219 }
220 
tick(double * frames,int numFrames)221 void b3WriteWavFile::tick(double *frames, int numFrames)
222 {
223 	int iFrames = 0;
224 	int j, nChannels = m_data->m_numChannels;
225 
226 	for (int i = 0; i < numFrames; i++)
227 	{
228 		for (j = 0; j < nChannels; j++)
229 		{
230 			double sample = frames[iFrames++];
231 			if (sample < -1.)
232 			{
233 				sample = -1.;
234 				m_data->m_clipped = true;
235 			}
236 			if (sample > 1)
237 			{
238 				sample = 1.;
239 				m_data->m_clipped = true;
240 			}
241 
242 			if (m_data->m_dataType == B3_FLOAT32)
243 			{
244 				m_data->m_floatBuffer.push_back((float)sample);
245 			}
246 			else
247 			{
248 				m_data->m_doubleBuffer.push_back(sample);
249 			}
250 
251 			flushData(m_data->m_bufferSize);
252 		}
253 
254 		m_data->m_frameCounter++;
255 	}
256 }
257 
flushData(int bufferSize)258 void b3WriteWavFile::flushData(int bufferSize)
259 {
260 	if (m_data->m_dataType == B3_FLOAT32)
261 	{
262 		if (m_data->m_floatBuffer.size() >= bufferSize)
263 		{
264 			fwrite(&m_data->m_floatBuffer[0], sizeof(float), m_data->m_floatBuffer.size(), m_data->m_file);
265 			m_data->m_floatBuffer.resize(0);
266 		}
267 	}
268 	else
269 	{
270 		if (m_data->m_doubleBuffer.size() >= bufferSize)
271 		{
272 			fwrite(&m_data->m_doubleBuffer[0], sizeof(double), m_data->m_doubleBuffer.size(), m_data->m_file);
273 			m_data->m_doubleBuffer.resize(0);
274 		}
275 	}
276 }