|
4 | 4 | #include <utility>
|
5 | 5 | #include <math.h>
|
6 | 6 | #include "FileHandler.h"
|
| 7 | +#include "WaveFileHandler.h" |
7 | 8 |
|
8 | 9 | using namespace std;
|
9 | 10 |
|
10 | 11 | // The default byte-write order is little-endian
|
11 | 12 | // It changes only for writing strings, i.e. big-endian (the natural order) is used.
|
12 | 13 |
|
13 |
| -class WaveFileHandler { |
14 |
| -private: |
15 |
| - FileHandler rawFileHandler; |
16 |
| - unsigned int dataSubChunkSizeLocation{}; |
17 |
| - unsigned int dataStartLocation{}; |
18 |
| - // Size of a single sample/frame in bits |
19 |
| - // 8 bits = 8, 16 bits = 16, etc. |
20 |
| - unsigned short bitsPerSample; |
21 | 14 |
|
22 |
| - void setDataSubChunkSizeLocation(unsigned int locationBytes) { |
23 |
| - dataSubChunkSizeLocation = locationBytes; |
24 |
| - } |
25 |
| - void setDataStartLocation(unsigned int locationBytes) { |
26 |
| - dataStartLocation = locationBytes; |
27 |
| - } |
28 |
| - |
29 |
| - /** |
30 |
| - * Write RIFF header |
31 |
| - */ |
32 |
| - void writeRiffChunkDescriptor() { |
33 |
| - // Contains the letters "RIFF" in ASCII form |
34 |
| - // (0x52494646 big-endian form). |
35 |
| - const string chunkID = "RIFF"; |
36 |
| - |
37 |
| - // 36 + SubChunk2Size, or more precisely: |
38 |
| - // 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size) |
39 |
| - // This is the size of the rest of the chunk |
40 |
| - // following this number. This is the size of the |
41 |
| - // entire file in bytes minus 8 bytes for the |
42 |
| - // two fields not included in this count: |
43 |
| - // ChunkID and ChunkSize. |
44 |
| - // Initialized to 0. |
45 |
| - const unsigned int chunkSize = 0; |
46 |
| - |
47 |
| - // Contains the letters "WAVE" |
48 |
| - // (0x57415645 big-endian form). |
49 |
| - const string format = "WAVE"; |
50 |
| - |
51 |
| - // Dumping the header to file with appropriate endianness. |
52 |
| - this->rawFileHandler.writeString(chunkID); |
53 |
| - this->rawFileHandler.writeBytes(chunkSize, sizeof(chunkSize)); |
54 |
| - this->rawFileHandler.writeString(format); |
55 |
| - } |
56 |
| - |
57 |
| - /** |
58 |
| - * Set chunk size when all headers and data have been written |
59 |
| - */ |
60 |
| - void setChunkSize() { |
61 |
| - size_t currentFileSize = this->rawFileHandler.currentSize(); |
62 |
| - unsigned int sizeToWrite = currentFileSize - 8; |
63 |
| - this->rawFileHandler.modifyBytes(sizeToWrite, 4, 4); |
64 |
| - } |
65 |
| - |
66 |
| - /** |
67 |
| - * Write fmt sub-chunk header |
68 |
| - * Currently assuming PCM mode. |
69 |
| - * TODO: Make this function generic to write other formats other than PCM as well. |
70 |
| - */ |
71 |
| - void writeFmtSubChunk() { |
72 |
| - // Bytes per sample (=2 for 16 bit resolution) |
73 |
| - const unsigned bytesPerSample = bitsPerSample / 8; |
74 |
| - |
75 |
| - // Contains the letters "fmt " |
76 |
| - // Notice the extra space after fmt to complete 4 bytes. |
77 |
| - // (0x666d7420 big-endian form). |
78 |
| - const string subchunk1ID = "fmt "; |
79 |
| - |
80 |
| - // 16 for PCM. This is the size of the |
81 |
| - // rest of the Subchunk which follows this number. |
82 |
| - const unsigned int subchunk1Size = 16; |
83 |
| - |
84 |
| - // PCM = 1 (i.e. Linear quantization) |
85 |
| - // Values other than 1 indicate some form of compression. |
86 |
| - const unsigned short audioFormat = 1; |
87 |
| - |
88 |
| - // Mono = 1, Stereo = 2, etc. |
89 |
| - const unsigned short numChannels = 1; |
90 |
| - |
91 |
| - // Sample rate 8000, 44100, etc. |
92 |
| - const unsigned int sampleRate = 44100; |
93 |
| - |
94 |
| - // ByteRate == SampleRate * NumChannels * BytesPerSample |
95 |
| - const unsigned int byteRate = sampleRate * numChannels * bytesPerSample; |
96 |
| - |
97 |
| - // BlockAlign == NumChannels * BytesPerSample |
98 |
| - // The number of bytes for one sample including all channels. |
99 |
| - const unsigned short blockAlign = numChannels * bytesPerSample; |
100 |
| - |
101 |
| - // TODO: Add extra params for non-PCM sounds. |
102 |
| - |
103 |
| - // Finally dumping the header to file. |
104 |
| - this->rawFileHandler.writeString(subchunk1ID); |
105 |
| - this->rawFileHandler.writeBytes(subchunk1Size, sizeof(subchunk1Size)); |
106 |
| - this->rawFileHandler.writeBytes(audioFormat, sizeof(audioFormat)); |
107 |
| - this->rawFileHandler.writeBytes(numChannels, sizeof(numChannels)); |
108 |
| - this->rawFileHandler.writeBytes(sampleRate, sizeof(sampleRate)); |
109 |
| - this->rawFileHandler.writeBytes(byteRate, sizeof(byteRate)); |
110 |
| - this->rawFileHandler.writeBytes(blockAlign, sizeof(blockAlign)); |
111 |
| - this->rawFileHandler.writeBytes(bitsPerSample, sizeof(bitsPerSample)); |
112 |
| - } |
113 |
| - |
114 |
| - /** |
115 |
| - * Write only the header of data sub-chunk |
116 |
| - * Actual data to be written by separate function |
117 |
| - */ |
118 |
| - void writeDataSubChunkHeader() { |
119 |
| - // Contains the letters "data" |
120 |
| - // (0x64617461 big-endian form). |
121 |
| - const string subChunk2ID = "data"; |
122 |
| - |
123 |
| - // Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8 |
124 |
| - // This is the number of bytes in the data. |
125 |
| - // You can also think of this as the size |
126 |
| - // of the rest of the sub-chunk following this number. |
127 |
| - // Initialize to 0 since there is no data initially. |
128 |
| - const int subChunk2Size = 0; |
129 |
| - |
130 |
| - this->rawFileHandler.writeString(subChunk2ID); |
131 |
| - this->setDataSubChunkSizeLocation(this->rawFileHandler.currentWriteHeadPosition()); |
132 |
| - this->rawFileHandler.writeBytes(subChunk2Size, sizeof(subChunk2Size)); |
133 |
| - this->setDataStartLocation(this->rawFileHandler.currentWriteHeadPosition()); |
134 |
| - } |
135 |
| - |
136 |
| - void writeDataSubChunkSize() { |
137 |
| - size_t currentFileSize = this->rawFileHandler.currentSize(); |
138 |
| - unsigned int sizeToWrite = currentFileSize - this->dataStartLocation; |
139 |
| - this->rawFileHandler.modifyBytes(sizeToWrite, this->dataSubChunkSizeLocation, 4); |
140 |
| - } |
141 |
| -public: |
142 |
| - explicit WaveFileHandler(string filename) { |
143 |
| - this->bitsPerSample = 16; |
144 |
| - this->rawFileHandler.initializeFile(std::move(filename)); |
145 |
| - this->writeRiffChunkDescriptor(); |
146 |
| - this->writeFmtSubChunk(); |
147 |
| - this->writeDataSubChunkHeader(); |
148 |
| - } |
149 |
| - |
150 |
| - template <typename T> |
151 |
| - void writeSample(T sample_data, size_t sample_size) { |
152 |
| - this->rawFileHandler.writeBytes(sample_data, sample_size); |
153 |
| - } |
154 |
| - |
155 |
| - void close() { |
156 |
| - // Set Chunk size at the end of all writes (headers and data) |
157 |
| - this->setChunkSize(); |
158 |
| - // Set data sub-chunk size |
159 |
| - this->writeDataSubChunkSize(); |
160 |
| - // Finally close the file |
161 |
| - this->rawFileHandler.close(); |
162 |
| - } |
163 |
| -}; |
164 | 15 |
|
165 | 16 | int main() {
|
166 | 17 | WaveFileHandler wave_file("sample.wav");
|
|
0 commit comments