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, ¤tPosition); 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 }