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 }