1 module soundtab.audio.loader; 2 3 import derelict.mpg123; 4 import dlangui.core.logger; 5 public import soundtab.audio.wavefile;; 6 7 private __gshared bool mpg123Loaded; 8 private __gshared bool mpg123Error; 9 10 bool loadMPG123() { 11 if (mpg123Loaded) 12 return true; 13 if (mpg123Error) 14 return false; 15 try { 16 DerelictMPG123.load(); 17 Log.i("libmpg123 shared library is loaded ok"); 18 mpg123Loaded = true; 19 mpg123_init(); 20 } catch (Exception e) { 21 Log.e("Cannot load libmpg123 shared library", e); 22 mpg123Error = true; 23 } 24 return mpg123Loaded; 25 } 26 27 void uninitMP3Decoder() { 28 if (mpg123Loaded) 29 mpg123_exit(); 30 } 31 32 float[] shortToFloat(short[] buf, int step = 1) { 33 float[] res = new float[buf.length / step]; 34 for(size_t i = 0; i < res.length; i++) { 35 res[i] = buf[i * step] / 32768.0f; 36 } 37 return res; 38 } 39 40 WaveFile loadSoundFileMP3(string filename, bool forceMono = false) { 41 import std..string : toStringz; 42 WaveFile res = new WaveFile(); 43 bool loaded = true; 44 if (!loadMPG123()) { 45 Log.e("No MPG123 library found - cannot decode MP3"); 46 return null; 47 } 48 int error = 0; 49 mpg123_handle * mh = mpg123_new(null, &error); 50 int status = mpg123_open(mh, filename.toStringz); 51 if (status == MPG123_OK) { 52 int sourceEncoding; 53 status = mpg123_getformat(mh, &res.sampleRate, &res.channels, &sourceEncoding); 54 if (status == MPG123_OK) { 55 Log.d("mp3 file rate=", res.sampleRate, " channels=", res.channels, " enc=", sourceEncoding); 56 int bufferSize = cast(int)mpg123_outblock(mh); 57 Log.d("buffer size=", bufferSize); 58 bufferSize *= 4; 59 ubyte[] buffer = new ubyte[bufferSize]; 60 short[] outbuffer; 61 short * pbuf = cast(short*)buffer.ptr; 62 outbuffer.assumeSafeAppend; 63 size_t done = 0; 64 size_t bytesRead = 0; 65 for (;;) { 66 status = mpg123_read(mh, buffer.ptr, bufferSize, &done); 67 if (status != MPG123_OK) { 68 Log.d("Error while decoding: ", res); 69 break; 70 } 71 bytesRead += bufferSize; 72 if (!done) { 73 break; 74 } 75 outbuffer ~= pbuf[0 .. done/2]; 76 } 77 Log.d("Bytes decoded: ", bytesRead, " outBufferLength=", outbuffer.length); 78 if (res.channels >= 1 && outbuffer.length > 0) { 79 int step = 1; 80 if (forceMono) { 81 step = res.channels; 82 res.channels = 1; 83 Log.d("Forcing MONO"); 84 } 85 res.data = shortToFloat(outbuffer, step); 86 loaded = true; 87 res.filename = filename; 88 res.frames = cast(int)(res.data.length / res.channels); 89 if (res.data.length % res.channels) 90 res.data.length = res.data.length - res.data.length % res.channels; 91 } 92 } 93 94 mpg123_close(mh); 95 } 96 97 mpg123_delete(mh); 98 if (loaded) { 99 Log.i("Loaded sound file ", res.filename, " : sampleRate=", res.sampleRate, ", channels=", res.channels, " frames=", res.frames); 100 return res; // loaded ok 101 } 102 // failed 103 if (res) 104 destroy(res); 105 return null; 106 } 107 108 private uint decodeUint(ubyte[] buf, int pos) { 109 if (pos + 4 > buf.length) 110 return 0; 111 return cast(uint)buf[pos] | (cast(uint)buf[pos + 1] << 8) | (cast(uint)buf[pos + 2] << 16) | (cast(uint)buf[pos + 3] << 24); 112 } 113 114 private ushort decodeUshort(ubyte[] buf, int pos) { 115 if (pos + 2 > buf.length) 116 return 0; 117 return cast(ushort)buf[pos] | (cast(uint)buf[pos + 1] << 8); 118 } 119 120 float decode24bitSample(ubyte[] buf, size_t pos) { 121 uint v = cast(uint)buf[pos] | (cast(uint)buf[pos + 1] << 8) | (cast(uint)buf[pos + 2] << 16); 122 if (v & 0x800000) // sign extension 123 v |= 0xFF000000; 124 return (cast(int)v) / cast(float)(0x800000); 125 } 126 127 128 129 struct RIFFChunkIterator { 130 ubyte[] content; 131 uint pos; 132 ubyte[] chunkName; 133 uint chunkSize; 134 ubyte[] chunkData; 135 bool init(ubyte[] data) { 136 content = data; 137 if (!content) 138 return false; 139 if (content.length < 100) 140 return false; 141 if (content[0..4] != ['R', 'I', 'F', 'F']) 142 return false; 143 if (content[8..12] != ['W', 'A', 'V', 'E']) 144 return false; 145 uint sz = decodeUint(content, 4); 146 if (sz + 8 != content.length) 147 return false; 148 return nextChunk(12); 149 } 150 bool nextChunk(uint chunkFileOffset) { 151 pos = chunkFileOffset; 152 if (pos + 8 > content.length) 153 return false; 154 chunkName = content[pos .. pos + 4]; 155 chunkSize = decodeUint(content, pos + 4); 156 if (pos + 8 + chunkSize > content.length) 157 return false; // invalid chunk size 158 chunkData = content[pos + 8 .. pos + 8 + chunkSize]; 159 return true; 160 } 161 bool nextChunk() { 162 return nextChunk(pos + 8 + chunkSize); 163 } 164 } 165 166 WaveFile loadSoundFileWAV(string filename, bool forceMono = false) { 167 immutable ushort FORMAT_PCM = 1; 168 immutable ushort FORMAT_FLOAT = 3; 169 import std.file : read; 170 import std..string : toStringz; 171 ubyte[] content; 172 try { 173 content = cast(ubyte[])read(filename); 174 } catch (Exception e) { 175 // 176 } 177 RIFFChunkIterator riff; 178 if (!riff.init(content)) { 179 Log.e("Invalid RIFF file header in file ", filename); 180 return null; 181 } 182 ubyte[] formatData; 183 ubyte[] data; 184 do { 185 if (riff.chunkName == ['f', 'm', 't', ' ']) { 186 formatData = riff.chunkData; 187 } else if (riff.chunkName == ['d', 'a', 't', 'a']) { 188 data = riff.chunkData; 189 } 190 } while (riff.nextChunk()); 191 192 if (!formatData) { 193 Log.e("fmt chunk not found in file ", filename); 194 return null; 195 } 196 if (!data) { 197 Log.e("data chunk not found in file ", filename); 198 return null; 199 } 200 if (formatData.length < 16 || formatData.length > 100) 201 return null; 202 ushort audioFormat = decodeUshort(formatData, 0); 203 if (audioFormat != FORMAT_PCM && audioFormat != FORMAT_FLOAT) 204 return null; // not a PCM nor float 205 ushort nChannels = decodeUshort(formatData, 2); 206 uint sampleRate = decodeUint(formatData, 4); 207 uint byteRate = decodeUint(formatData, 8); 208 ushort nAlign = decodeUshort(formatData, 12); 209 ushort nBitsPerSample = decodeUshort(formatData, 14); 210 if ((nBitsPerSample != 16 && nBitsPerSample != 24 && audioFormat == FORMAT_PCM) || (nBitsPerSample != 32 && audioFormat == FORMAT_FLOAT)) 211 return null; 212 if (sampleRate < 11025 || sampleRate > 96000) 213 return null; 214 if (nAlign != nChannels * nBitsPerSample / 8) 215 return null; // invalid align 216 217 uint dataSize = cast(uint)data.length; 218 uint nSamples = dataSize / nAlign; 219 if (nSamples < 100) 220 return null; 221 WaveFile res = new WaveFile(); 222 res.filename = filename; 223 res.frames = nSamples; 224 res.sampleRate = sampleRate; 225 res.channels = nChannels; 226 int dstchannels = (forceMono ? 1 : nChannels); 227 res.data = new float[dstchannels * nSamples]; 228 for (int i = 0; i < nSamples; i++) { 229 for (int channel = 0; channel < nChannels; channel++) { 230 if (channel >= dstchannels) 231 break; 232 int index = i * nChannels + channel; 233 int dstindex = i * dstchannels + channel; 234 float dst = 0; 235 if (audioFormat == FORMAT_PCM) { 236 if (nBitsPerSample == 24) { 237 dst = decode24bitSample(data, index * 3); 238 } else { 239 ushort src = decodeUshort(data, (index * ushort.sizeof)); 240 dst = (cast(short)src) / 32767.0f; 241 } 242 } else { 243 // IEEE float format 244 union convertUnion { 245 uint intvalue; 246 float floatvalue; 247 } 248 convertUnion tmp; 249 250 tmp.intvalue = decodeUint(data, (index * uint.sizeof)); 251 dst = tmp.floatvalue; 252 } 253 res.data[dstindex] = dst; 254 } 255 } 256 bool loaded = true; 257 Log.i("Loaded sound file ", res.filename, " : sampleRate=", res.sampleRate, ", channels=", res.channels, " frames=", res.frames); 258 return res; 259 } 260 261 WaveFile loadSoundFile(string filename, bool forceMono = false) { 262 import std.algorithm : endsWith; 263 if (filename.endsWith(".wav") || filename.endsWith(".WAV")) { 264 WaveFile res = loadSoundFileWAV(filename, forceMono); 265 if (!res) 266 res = loadSoundFileMP3(filename, forceMono); // it could be MP3 inside WAV 267 return res; 268 } 269 if (filename.endsWith(".mp3") || filename.endsWith(".MP3")) { 270 return loadSoundFileMP3(filename, forceMono); 271 } 272 return null; 273 }