1 /*  This file is part of UKNCBTL.
2     UKNCBTL is free software: you can redistribute it and/or modify it under the terms
3 of the GNU Lesser General Public License as published by the Free Software Foundation,
4 either version 3 of the License, or (at your option) any later version.
5     UKNCBTL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
6 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 See the GNU Lesser General Public License for more details.
8     You should have received a copy of the GNU Lesser General Public License along with
9 UKNCBTL. If not, see <http://www.gnu.org/licenses/>. */
10 
11 // WavPcmFile.cpp
12 
13 #include "stdafx.h"
14 #include "WavPcmFile.h"
15 #include <stdio.h>
16 
17 
18 //////////////////////////////////////////////////////////////////////
19 
20 // WAV PCM format description: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
21 
22 static const char magic1[4] = { 'R', 'I', 'F', 'F' };
23 static const char magic2[4] = { 'W', 'A', 'V', 'E' };
24 static const char format_tag_id[4] = { 'f', 'm', 't', ' ' };
25 static const char data_tag_id[4] = { 'd', 'a', 't', 'a' };
26 
27 const int WAV_FORMAT_PCM = 1;
28 
29 struct WAVPCMFILE
30 {
31     FILE* fpFile;
32     int nChannels;
33     int nBitsPerSample;
34     int nSampleFrequency;
35     int nBlockAlign;
36     quint32 dwDataOffset;
37     quint32 dwDataSize;
38     quint32 dwCurrentPosition;
39     bool okWriting;
40 };
41 
WavPcmFile_GetFrequency(HWAVPCMFILE wavpcmfile)42 int WavPcmFile_GetFrequency(HWAVPCMFILE wavpcmfile)
43 {
44     if (wavpcmfile == INVALID_HANDLE_VALUE)
45         return 0;
46 
47     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
48 
49     return pWavPcm->nSampleFrequency;
50 }
51 
WavPcmFile_GetLength(HWAVPCMFILE wavpcmfile)52 quint32 WavPcmFile_GetLength(HWAVPCMFILE wavpcmfile)
53 {
54     if (wavpcmfile == INVALID_HANDLE_VALUE)
55         return 0;
56 
57     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
58 
59     return pWavPcm->dwDataSize / pWavPcm->nBlockAlign;
60 }
61 
WavPcmFile_GetPosition(HWAVPCMFILE wavpcmfile)62 quint32 WavPcmFile_GetPosition(HWAVPCMFILE wavpcmfile)
63 {
64     if (wavpcmfile == INVALID_HANDLE_VALUE)
65         return 0;
66 
67     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
68 
69     return pWavPcm->dwCurrentPosition;
70 }
71 
WavPcmFile_SetPosition(HWAVPCMFILE wavpcmfile,quint32 position)72 void WavPcmFile_SetPosition(HWAVPCMFILE wavpcmfile, quint32 position)
73 {
74     if (wavpcmfile == INVALID_HANDLE_VALUE)
75         return;
76 
77     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
78 
79     quint32 offsetInData = position * pWavPcm->nBlockAlign;
80     ::fseek(pWavPcm->fpFile, pWavPcm->dwDataOffset + offsetInData, SEEK_SET);
81 
82     pWavPcm->dwCurrentPosition = position;
83 }
84 
WavPcmFile_Create(LPCTSTR filename,int sampleRate)85 HWAVPCMFILE WavPcmFile_Create(LPCTSTR filename, int sampleRate)
86 {
87     const int bitsPerSample = 8;
88     const int channels = 1;
89     const int blockAlign = channels * bitsPerSample / 8;
90 
91     FILE* fpFileNew = ::_tfopen(filename, _T("w+b"));
92     if (fpFileNew == nullptr)
93         return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to create file
94 
95     // Prepare and write file header
96     quint8 consolidated_header[12 + 8 + 16 + 8];
97     ::memset(consolidated_header, 0, sizeof(consolidated_header));
98     size_t bytesWritten;
99 
100     memcpy(&consolidated_header[0], magic1, 4);  // RIFF
101     memcpy(&consolidated_header[8], magic2, 4);  // WAVE
102 
103     memcpy(&consolidated_header[12], format_tag_id, 4);  // fmt
104     *((quint32*)(consolidated_header + 16)) = 16;  // Size of "fmt" chunk
105     *((quint16*)(consolidated_header + 20)) = WAV_FORMAT_PCM;  // AudioFormat = PCM
106     *((quint16*)(consolidated_header + 22)) = channels;  // NumChannels = mono
107     *((quint32*)(consolidated_header + 24)) = sampleRate;  // SampleRate
108     *((quint32*)(consolidated_header + 28)) = sampleRate * channels * bitsPerSample / 8;  // ByteRate
109     *((quint16*)(consolidated_header + 32)) = blockAlign;
110     *((quint16*)(consolidated_header + 34)) = bitsPerSample;
111 
112     memcpy(&consolidated_header[36], data_tag_id, 4);  // data
113 
114     // Write consolidated header
115     bytesWritten = ::fwrite(consolidated_header, 1, sizeof(consolidated_header), fpFileNew);
116     if (bytesWritten != sizeof(consolidated_header))
117     {
118         ::fclose(fpFileNew);
119         return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to write consolidated header
120     }
121 
122     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) ::malloc(sizeof(WAVPCMFILE));
123     if (pWavPcm == nullptr)
124     {
125         ::fclose(fpFileNew);
126         return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to allocate memory
127     }
128     memset(pWavPcm, 0, sizeof(WAVPCMFILE));
129     pWavPcm->fpFile = fpFileNew;
130     pWavPcm->nChannels = channels;
131     pWavPcm->nSampleFrequency = sampleRate;
132     pWavPcm->nBitsPerSample = bitsPerSample;
133     pWavPcm->nBlockAlign = blockAlign;
134     pWavPcm->dwDataOffset = sizeof(consolidated_header);
135     pWavPcm->dwDataSize = 0;
136     pWavPcm->okWriting = true;
137 
138     WavPcmFile_SetPosition((HWAVPCMFILE) pWavPcm, 0);
139 
140     return (HWAVPCMFILE) pWavPcm;
141 }
142 
WavPcmFile_Open(LPCTSTR filename)143 HWAVPCMFILE WavPcmFile_Open(LPCTSTR filename)
144 {
145     FILE* fpFileOpen = ::_tfopen(filename, _T("rb"));
146     if (fpFileOpen == nullptr)
147         return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to open file
148 
149     quint32 offset = 0;
150     size_t bytesRead;
151     ::fseek(fpFileOpen, 0, SEEK_END);
152     quint32 fileSize = ::ftell(fpFileOpen);
153     ::fseek(fpFileOpen, 0, SEEK_SET);
154 
155     quint8 fileHeader[12];
156     bytesRead = ::fread(fileHeader, 1, sizeof(fileHeader), fpFileOpen);
157     if (bytesRead != sizeof(fileHeader) ||
158         memcmp(&fileHeader[0], magic1, 4) != 0 ||
159         memcmp(&fileHeader[8], magic2, 4) != 0)
160     {
161         ::fclose(fpFileOpen);
162         return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to read file header OR invalid 'RIFF' tag OR invalid 'WAVE' tag
163     }
164     offset += (quint32)bytesRead;
165 
166     quint32 statedSize = *((quint32*)(fileHeader + 4)) + 8;
167     if (statedSize > fileSize)
168         statedSize = fileSize;
169 
170     quint8 tagHeader[8];
171     quint16 formatTag[8];
172     bool formatSpecified = false;
173     int formatType, channels = 1, bitsPerSample, blockAlign;
174     quint32 sampleFrequency, bytesPerSecond, dataOffset, dataSize = 0;
175     while (offset < statedSize)
176     {
177         bytesRead = ::fread(tagHeader, 1, sizeof(tagHeader), fpFileOpen);
178         if (bytesRead != sizeof(tagHeader))
179         {
180             ::fclose(fpFileOpen);
181             return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to read tag header
182         }
183         offset += (quint32)bytesRead;
184 
185         quint32 tagSize = *(quint32*)(tagHeader + 4);
186         if (!memcmp(tagHeader, format_tag_id, 4))
187         {
188             if (formatSpecified || tagSize < sizeof(formatTag))
189             {
190                 ::fclose(fpFileOpen);
191                 return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Wrong tag header
192             }
193             formatSpecified = true;
194 
195             bytesRead = ::fread(formatTag, 1, sizeof(formatTag), fpFileOpen);
196             if (bytesRead != sizeof(formatTag))
197             {
198                 ::fclose(fpFileOpen);
199                 return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to read format tag
200             }
201 
202             formatType = formatTag[0];
203             channels = formatTag[1];
204             sampleFrequency = formatTag[2];
205             bytesPerSecond = formatTag[4];
206             blockAlign = formatTag[6];
207             bitsPerSample = formatTag[7];
208 
209             if (formatType != WAV_FORMAT_PCM)
210             {
211                 ::fclose(fpFileOpen);
212                 return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Unsupported format
213             }
214             if (sampleFrequency * bitsPerSample * channels / 8 != bytesPerSecond ||
215                 (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 32))
216             {
217                 ::fclose(fpFileOpen);
218                 return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Wrong format tag
219             }
220         }
221         else if (!memcmp(tagHeader, data_tag_id, 4))
222         {
223             if (!formatSpecified)
224             {
225                 ::fclose(fpFileOpen);
226                 return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Wrong tag
227             }
228 
229             dataOffset = offset;
230             dataSize = tagSize;
231         }
232         else  // Ignore all other tags
233         {
234         }
235 
236         offset += tagSize;
237         ::fseek(fpFileOpen, offset, SEEK_SET);
238     }
239 
240     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) ::malloc(sizeof(WAVPCMFILE));
241     if (pWavPcm == nullptr)
242     {
243         ::fclose(fpFileOpen);
244         return (HWAVPCMFILE) INVALID_HANDLE_VALUE;  // Failed to allocate memory
245     }
246     ::memset(pWavPcm, 0, sizeof(WAVPCMFILE));
247     pWavPcm->fpFile = fpFileOpen;
248     pWavPcm->nChannels = channels;
249     pWavPcm->nSampleFrequency = sampleFrequency;
250     pWavPcm->nBitsPerSample = bitsPerSample;
251     pWavPcm->nBlockAlign = blockAlign;
252     pWavPcm->dwDataOffset = dataOffset;
253     pWavPcm->dwDataSize = dataSize;
254     pWavPcm->okWriting = false;
255 
256     WavPcmFile_SetPosition((HWAVPCMFILE) pWavPcm, 0);
257 
258     return (HWAVPCMFILE) pWavPcm;
259 }
260 
WavPcmFile_Close(HWAVPCMFILE wavpcmfile)261 void WavPcmFile_Close(HWAVPCMFILE wavpcmfile)
262 {
263     if (wavpcmfile == INVALID_HANDLE_VALUE)
264         return;
265 
266     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
267 
268     if (pWavPcm->okWriting)
269     {
270         size_t bytesWritten;
271         // Write data chunk size
272         ::fseek(pWavPcm->fpFile, 4, SEEK_SET);
273         quint32 chunkSize = 36 + pWavPcm->dwDataSize;
274         bytesWritten = ::fwrite(&chunkSize, 1, 4, pWavPcm->fpFile);
275         // Write data subchunk size
276         ::fseek(pWavPcm->fpFile, 40, SEEK_SET);
277         bytesWritten = ::fwrite(&(pWavPcm->dwDataSize), 1, 4, pWavPcm->fpFile);
278     }
279 
280     ::fclose(pWavPcm->fpFile);
281     pWavPcm->fpFile = nullptr;
282     ::free(pWavPcm);
283 }
284 
WavPcmFile_WriteOne(HWAVPCMFILE wavpcmfile,unsigned int value)285 bool WavPcmFile_WriteOne(HWAVPCMFILE wavpcmfile, unsigned int value)
286 {
287     if (wavpcmfile == INVALID_HANDLE_VALUE)
288         return false;
289 
290     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
291     if (!pWavPcm->okWriting)
292         return false;
293     ASSERT(pWavPcm->nBitsPerSample == 8);
294     ASSERT(pWavPcm->nChannels == 1);
295 
296     quint8 data = (value >> 24) & 0xff;
297 
298     size_t bytesWritten = ::fwrite(&data, 1, 1, pWavPcm->fpFile);
299     if (bytesWritten != 1)
300         return false;
301 
302     pWavPcm->dwCurrentPosition++;
303     pWavPcm->dwDataSize += pWavPcm->nBlockAlign;
304 
305     return true;
306 }
307 
WavPcmFile_ReadOne(HWAVPCMFILE wavpcmfile)308 unsigned int WavPcmFile_ReadOne(HWAVPCMFILE wavpcmfile)
309 {
310     if (wavpcmfile == INVALID_HANDLE_VALUE)
311         return 0;
312 
313     WAVPCMFILE* pWavPcm = (WAVPCMFILE*) wavpcmfile;
314     if (pWavPcm->okWriting)
315         return 0;
316 
317     // Read one sample
318     quint32 bytesToRead = pWavPcm->nBlockAlign;
319     size_t bytesRead;
320     quint8 data[16];
321     bytesRead = ::fread(data, 1, bytesToRead, pWavPcm->fpFile);
322     if (bytesRead != bytesToRead)
323         return 0;
324 
325     pWavPcm->dwCurrentPosition++;
326 
327     // Decode first channel
328     unsigned int value = 0;
329     switch (pWavPcm->nBitsPerSample)
330     {
331     case 8:
332         value = *data;
333         value = value << 24;
334         break;
335     case 16:
336         value = *((quint16*)data);
337         value = value << 16;
338         break;
339     case 32:
340         value = *((quint32*)data);
341         break;
342     }
343 
344     return value;
345 }
346 
347 
348 //////////////////////////////////////////////////////////////////////
349