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 }