1 module armos.audio.buffer;
2 
3 import derelict.openal.al;
4 import derelict.ogg.ogg;
5 import derelict.vorbis.vorbis;
6 import derelict.vorbis.enc;
7 import derelict.vorbis.file;
8 
9 import armos.audio.spectrum;
10 import armos.audio.spectrumanalyzer:BpmDetectionSource;
11 /++
12 +/
13 class Buffer {
14     public{
15         ///
16         this(){
17             alGenBuffers(1, cast(uint*)&_id); 
18         }
19         
20         ///
21         ~this(){
22             alDeleteBuffers(1, cast(uint*)&_id); 
23         }
24         
25         ///
26         Buffer load(string path){
27             import std.file;
28             import std.path;
29             switch (extension(path)) {
30                 case ".wav":
31                     loadWav(path);
32                     break;
33                 case ".ogg":
34                     loadOgg(path);
35                     break;
36                 default:
37                     assert(0, "invalid format");
38             }
39             return this;
40         }
41         
42         ///
43         Buffer loadWav(string path){
44             import std.file;
45             loadWav(cast(ubyte[])read(path));
46             return this;
47         }
48         
49         ///
50         Buffer loadWav(ubyte[] buffer){
51             load(buffer.decodeWave);
52             return this;
53         }
54         
55         ///
56         Buffer load(Wave wave){
57             import std.conv;
58             _samplingRate = wave.sampleRate.to!int;
59             _numChannels = wave.numChannels.to!int;
60             _depth = wave.depth.to!int;
61             _format = formatFrom(wave.numChannels, wave.depth);
62             _pcm = wave.data;
63             
64             if(!_channels) setupChannnels;
65             
66             alBufferData(_id,
67                          _format,
68                          wave.data.ptr,
69                          wave.data.length.to!int,
70                          wave.sampleRate.to!int);
71             return this;
72         }
73         
74         ///
75         Buffer loadOgg(string path){
76             DerelictOgg.load();
77             DerelictVorbis.load();
78             DerelictVorbisEnc.load();
79             DerelictVorbisFile.load();
80             
81             OggVorbis_File vorbisFile;
82             import std.string;
83             if (ov_fopen(path.toStringz, &vorbisFile) != 0){
84                 assert(0);
85             }
86             vorbis_info* info = ov_info(&vorbisFile, -1);
87             
88             import std.conv;
89             import std.algorithm;
90             import std.array;
91             byte[] tempData = new byte[4096];
92             int currentPosition = 0;
93             
94             import std.array:appender;
95             auto dataApp = appender!(byte[]);
96             while(true){
97                 size_t tempSize = ov_read(&vorbisFile, tempData.ptr, 4096, 0, 2, 1, &currentPosition);
98                 if(tempSize <= 0)break;
99                 dataApp.put(tempData[0..tempSize]);
100             }
101             byte[] data = dataApp.data;
102             
103             _samplingRate = info.rate;
104             
105             import std.range;
106             auto pcmApp = appender!(short[]);
107             foreach (offset; data.length.iota) {
108                 if (offset % 2 == 0) {
109                     //convert ubyte to 16 bit little endian integer
110                     short val = cast(short)(cast(ubyte)(data[offset]) | (cast(ubyte)(data[offset + 1])<<8));
111                     pcmApp.put(cast(short)((val & 0x8000) ? val | 0xFFFF0000 : val));
112                 }
113             } 
114             _pcm = pcmApp.data;
115             
116             _numChannels = info.channels;
117             _depth = 16;
118             _format = formatFrom(info.channels, 16);
119             
120             if(!_channels) setupChannnels;
121                 
122             alBufferData(_id,
123                          _format, 
124                          _pcm.ptr,
125                          _pcm.length.to!int*2,
126                          info.rate);
127             
128             ov_clear(&vorbisFile);
129             return this;
130         }
131         
132         //TODO
133         // void loadOgg(ubyte[] buffer){
134         
135         ///
136         int id()const{return _id;}
137         
138         ///
139         size_t samplingRate()const{
140             return _samplingRate;
141         }
142         
143         ///
144         int numChannels()const{return _numChannels;}
145         
146         ///
147         int depth()const{return _depth;}
148         
149         ///
150         ALenum format()const{return _format;}
151         
152         short[] pcm(){
153             return _pcm;
154         }
155         
156         ///
157         size_t channelLength()const{
158             return _pcm.length/_numChannels;
159         }
160         
161         ///
162         Buffer range(in float startSec, in float finishSec){
163             _start  = startSec;
164             _finish = finishSec;
165             float sampleRate = _samplingRate;
166             import std.conv;
167             short[] rangedPcm;
168             if(finishSec < 0f){
169                 rangedPcm = _pcm[(_start*sampleRate).to!int*2..$];
170             }else{
171                 rangedPcm = _pcm[(_start*sampleRate).to!int*2..(_finish*sampleRate).to!int*2];
172             }
173             
174             if(!_channels) setupChannnels;
175             
176             alBufferData(_id,
177                          _format, 
178                          rangedPcm.ptr,
179                          rangedPcm.length.to!int*2,
180                          _samplingRate.to!int);
181             return this;
182         }
183         
184         ///
185         Spectrum!R spectrumAtIndex(R = double, T = short)(in size_t channelIndex, in size_t index, in size_t bufferSize)const
186         in{
187             assert(index < channelLength);
188         }
189         body{
190             const channel = _channels[channelIndex];
191             
192             short[] result = new short[](bufferSize*2);
193             for (size_t i = index-(bufferSize*2-1), j = 0; i <= index; i++, j++) {
194                 if(0 <= i){
195                     result[j] = channel[i];
196                 }
197             }
198             assert(result.length == bufferSize*2);
199             import armos.audio.spectrumanalyzer;
200             return analyzeSpectrum!R(result, _samplingRate);
201         }
202         
203         ///
204         Buffer detectBpm(in size_t bufferSize, in BpmDetectionSource sourceType = BpmDetectionSource.Beats)
205         in{
206             assert(0<channelLength);
207         }
208         body{
209             alias F = double;
210             //Merge channels.
211             F[] mergedSample = new F[](channelLength);
212             for (int i = 0; i < channelLength; i++) {
213                 F sum = 0;
214                 for (int c = 0; c < numChannels; c++) {
215                     sum += _channels[c][i];
216                 }
217                 mergedSample[i] = sum;
218             }
219             
220             import armos.audio.spectrumanalyzer;
221             immutable t = analyzeBpmAndPhase!double(mergedSample, _samplingRate, bufferSize, sourceType);
222             _bpm = t[0];
223             _phase= t[1];
224             return this;
225         }
226         
227         ///
228         float bpm()const{
229             return _bpm;
230         }
231         
232         ///
233         float phase()const{
234             return _phase;
235         }
236     }//public
237 
238     private{
239         int _id;
240         short[] _pcm;
241         short[][] _channels;
242         size_t _samplingRate;
243         int _numChannels;
244         int _depth;
245         float _start;
246         float _finish;
247         ALenum _format;
248         float _bpm;
249         float _phase;
250         
251         void setupChannnels()
252         in{
253             assert(0 < _pcm.length);
254             assert(0 < _numChannels);
255         }
256         body{
257             import std.range:Appender;
258             import std.stdio;
259             for (int c = 0; c < _numChannels; c++) {
260                 _channels ~= new short[](0);
261             }
262             
263             Appender!(short[])[] channelApps = new Appender!(short[])[](_numChannels);
264             for (int i = 0; i < _pcm.length/_numChannels; i++) {
265                 for (int c = 0; c < _numChannels; c++) {
266                     immutable pcmIndex = i*_numChannels+c;
267                     channelApps[c].put(_pcm[pcmIndex]);
268                 }
269             }
270             assert(_channels.length == _numChannels);
271             for (int c = 0; c < _numChannels; c++) {
272                 _channels[c] = channelApps[c].data;
273             }
274         }
275         
276     }//private
277 }//class Buffer
278 
279 private{
280     
281     ALenum formatFrom(size_t numChannels, size_t depth){
282         ALenum f;
283         if(numChannels == 1){
284             if(depth == 8){
285                 f = AL_FORMAT_MONO8;
286             }else{
287                 f = AL_FORMAT_MONO16;
288             }
289         }else{
290             if(depth == 8){
291                 f = AL_FORMAT_STEREO8;
292             }else{
293                 f = AL_FORMAT_STEREO16;
294             }
295         }
296         return f;
297     }
298     
299     unittest{
300         assert(formatFrom(1,8)  == AL_FORMAT_MONO8);
301         assert(formatFrom(1,16) == AL_FORMAT_MONO16);
302         assert(formatFrom(2,8)  == AL_FORMAT_STEREO8);
303         assert(formatFrom(2,16) == AL_FORMAT_STEREO16);
304     }
305     
306     /++
307         +/
308     class Wave {
309         public{
310             size_t numChannels(){return _format.channels;}
311             size_t sampleRate(){return _format.samplePerSecond;}
312             size_t depth(){return _format.bitsPerSample;}
313             
314             short[] data(){return _data;}
315         }//public
316 
317         private{
318             WaveFileFormat _format;
319             short[] _data;
320         }//private
321     }//class Wave
322     
323     // I wish to thank alpha_kai_NET for providing this decoder.
324     // cf. http://qiita.com/alpha_kai_NET/items/7346636d247449e8b342
325     Wave decodeWave(ubyte[] buf){
326         import std.stdio;
327         size_t b = 0,
328                e = RiffChunk.sizeof;
329 
330         RiffChunk* riff = cast(RiffChunk*)buf[b..e];
331 
332         WaveFileFormat* format;
333         ubyte[] bufPcm;
334         short[] pcm;
335 
336         if(riff.head.id != "RIFF") {
337             writeln("Invalid WAV format was given");
338             return null; 
339         } 
340 
341         if (riff.format != "WAVE") {
342             writeln("Invalid RIFF chunk format. Given file is not WAVE");
343             return null; 
344         }
345 
346         ChunkHead* chunk;
347 
348         b = e;
349         while (b < buf.length) {
350             e = b + ChunkHead.sizeof;
351             chunk = cast(ChunkHead*)buf[b..e];
352 
353             if (chunk.size < 0) {
354                 writeln("ERROR! Invalid chunk size");
355                 return null; 
356             }
357 
358             b = e;
359 
360             if (chunk.id == "fmt ") {
361                 import std.algorithm : min;
362                 e = b + min(chunk.size, WaveFormatChunk.sizeof);
363                 format = cast(WaveFileFormat*)buf[b..e];
364 
365                 b = e;
366             } else if (chunk.id == "data") {
367                 e = b + chunk.size;
368 
369                 bufPcm = buf[b..e];
370                 pcm.length = bufPcm.length / 2;
371 
372                 size_t realIdx;
373 
374                 import std.range;
375                 foreach (offset; bufPcm.length.iota) {
376                     if (offset % 2 == 0) {
377                         //convert ubyte to 16 bit little endian integer
378                         short val = cast(short)(bufPcm[offset] | (bufPcm[offset + 1] << 8));
379                         pcm[realIdx++] = cast(short)((val & 0x8000) ? val | 0xFFFF0000 : val);
380                     }
381                 } 
382 
383                 b = e;
384             } else {//skip the others chunk
385                 b = e + chunk.size;
386             }
387         }
388 
389         Wave wav = new Wave;
390         wav._format = *format;
391         wav._data = pcm;
392         return wav;
393     }
394     
395     struct ChunkHead {
396         char[4] id;
397         uint size;
398     }
399 
400     struct RiffChunk {
401         ChunkHead head;
402         char[4] format;
403     }
404 
405     struct WaveFileFormat {
406         ushort audioFormat,
407                channels;
408         uint   samplePerSecond,
409                bytesPerSecond;
410         ushort blockAlign,
411                bitsPerSample;
412     }
413 
414     struct WaveFormatChunk {
415         ChunkHead chunk;
416         WaveFileFormat format;
417     }
418 
419 }