1 module soundtab.audio.audiosource;
2 
3 enum AUDIO_SOURCE_SILENCE_FLAG = 1;
4 
5 enum SampleFormat {
6     signed16,
7     signed32,
8     float32
9 }
10 
11 /// for float->short conversion
12 union ShortConv {
13     short value;
14     byte[2] bytes;
15 }
16 
17 /// for float->byte conversion
18 union FloatConv {
19     float value;
20     byte[4] bytes;
21 }
22 
23 /// limit int value to fit short range -32768..32767
24 void limitShortRange(ref int value) {
25     if (value < -32768)
26         value = -32768;
27     else if (value > 32767)
28         value = 32767;
29 }
30 
31 
32 class AudioSource {
33 
34     import core.sync.mutex;
35 
36     protected Mutex _lock;
37 
38 
39     this() {
40         _lock = new Mutex();
41     }
42     void lock() {
43         _lock.lock();
44     }
45     void unlock() {
46         _lock.unlock();
47     }
48 
49     /// get audio source volume (0 .. 1.0f)
50     @property float volume() {
51         return _volume;
52     }
53 
54     /// set audio source volume (0 .. 1.0f)
55     @property AudioSource volume(float v) {
56         lock();
57         scope(exit)unlock();
58         if (v < 0.00001) {
59             // volume = 0
60             _volume = 0;
61             _zeroVolume = true;
62             _unityVolume = false;
63         } else if (v >= 0.999) {
64             // volume = 100%
65             _volume = 1.0f;
66             _zeroVolume = false;
67             _unityVolume = true;
68         } else {
69             // arbitrary volume
70             _zeroVolume = false;
71             _unityVolume = false;
72             _volume = v;
73         }
74         return this;
75     }
76 
77     protected float _volume = 1.0f;
78     protected bool _zeroVolume = false;
79     protected bool _unityVolume = true;
80     protected SampleFormat sampleFormat = SampleFormat.float32;
81     protected int samplesPerSecond = 44100;
82     protected int channels = 2;
83     protected int bitsPerSample = 16;
84     protected int blockAlign = 4;
85 
86     /// recalculate some parameters after format change or after generation
87     protected void calcParams() {
88     }
89 
90     void setFormat(SampleFormat format, int channels, int samplesPerSecond, int bitsPerSample, int blockAlign) {
91         lock();
92         scope(exit)unlock();
93 
94         this.sampleFormat = format;
95         this.channels = channels;
96         this.samplesPerSecond = samplesPerSecond;
97         this.bitsPerSample = bitsPerSample;
98         this.blockAlign = blockAlign;
99         calcParams();
100         onFormatChanged();
101     }
102 
103     protected void onFormatChanged() {
104     }
105 
106     /// put samples in int format
107     protected void putSamples(ubyte * buf, int sample1, int sample2) {
108         if (sampleFormat == SampleFormat.float32) {
109             float * floatBuf = cast(float*)buf;
110             float sample1f = cast(float)(sample1 / 32768.0f);
111             *(floatBuf++) = sample1f;
112             if (channels > 1) {
113                 float sample2f = cast(float)(sample2 / 32768.0f);
114                 *(floatBuf++) = sample2f;
115                 if (channels > 2) {
116                     *(floatBuf++) = sample1f;
117                     if (channels > 3) {
118                         *(floatBuf++) = sample2f;
119                         if (channels > 4) {
120                             *(floatBuf++) = sample1f;
121                             if (channels > 5)
122                                 *(floatBuf++) = sample2f;
123                         }
124                     }
125                 }
126             }
127         } else {
128             limitShortRange(sample1);
129             limitShortRange(sample2);
130             short * shortBuf = cast(short*)buf;
131             *(shortBuf++) = cast(short)sample1;
132             if (channels > 1)
133                 *(shortBuf++) = cast(short)sample2;
134             if (channels > 2)
135                 *(shortBuf++) = cast(short)sample1;
136             if (channels > 3)
137                 *(shortBuf++) = cast(short)sample2;
138             if (channels > 4)
139                 *(shortBuf++) = cast(short)sample1;
140             if (channels > 5)
141                 *(shortBuf++) = cast(short)sample2;
142         }
143     }
144 
145     /// put samples in float format
146     protected void putSamples(ubyte * buf, float sample1, float sample2) {
147         if (sampleFormat == SampleFormat.float32) {
148             float * floatBuf = cast(float*)buf;
149             *(floatBuf++) = sample1;
150             if (channels > 1) {
151                 *(floatBuf++) = sample2;
152                 if (channels > 2) {
153                     *(floatBuf++) = sample1;
154                     if (channels > 3) {
155                         *(floatBuf++) = sample2;
156                         if (channels > 4) {
157                             *(floatBuf++) = sample1;
158                             if (channels > 5)
159                                 *(floatBuf++) = sample2;
160                         }
161                     }
162                 }
163             }
164         } else if (sampleFormat == SampleFormat.signed16) {
165             short * shortBuf = cast(short*)buf;
166             int sample1i = cast(int)(sample1 * 32767.0f);
167             int sample2i = cast(int)(sample2 * 32767.0f);
168             limitShortRange(sample1i);
169             limitShortRange(sample2i);
170             *(shortBuf++) = cast(short)sample1i;
171             if (channels > 1)
172                 *(shortBuf++) = cast(short)sample2i;
173             if (channels > 2)
174                 *(shortBuf++) = cast(short)sample1i;
175             if (channels > 3)
176                 *(shortBuf++) = cast(short)sample2i;
177             if (channels > 4)
178                 *(shortBuf++) = cast(short)sample1i;
179             if (channels > 5)
180                 *(shortBuf++) = cast(short)sample2i;
181         } else {
182             // unsupported format
183         }
184     }
185 
186     protected void generateSilence(int frameCount, ubyte * buf) {
187         for (int i = 0; i < frameCount; i++) {
188             putSamples(buf, 0, 0);
189             buf += blockAlign;
190         }
191     }
192 
193     /// load data into buffer
194     bool loadData(int frameCount, ubyte * buf, ref uint flags) {
195         // silence
196         // override to render sound
197         flags = 0;
198         generateSilence(frameCount, buf);
199         return true;
200     }
201 }
202 
203 /// Mixer which can mix several audio sources
204 class Mixer : AudioSource {
205     private AudioSource[] _sources;
206 
207     this() {
208         _sources.assumeSafeAppend;
209         _mixBuffer = new float[4096];
210         _mixBuffer.assumeSafeAppend;
211         _sourceBuffer = new float[4096];
212         _sourceBuffer.assumeSafeAppend;
213     }
214 
215     private AudioSource[] getSourcesSnapshot() {
216         lock();
217         scope(exit)unlock();
218         AudioSource[] res = _sources.dup();
219         return res;
220     }
221 
222     /// add source to mixer
223     void addSource(AudioSource source) {
224         lock();
225         scope(exit)unlock();
226         foreach(s; _sources)
227             if (s is source)
228                 return;
229         _sources ~= source;
230         setSourceFormat(source);
231     }
232 
233     /// remove source from mixer
234     void removeSource(AudioSource source) {
235         lock();
236         scope(exit)unlock();
237         for (int i = 0; i < _sources.length; i++) {
238             if (_sources[i] is source) {
239                 for (int j = i; j + 1 < _sources.length; j++)
240                     _sources[j] = _sources[j + 1];
241                 _sources[$ - 1] = null;
242                 _sources.length = _sources.length - 1;
243                 return;
244             }
245         }
246     }
247 
248     /// pass current format to source
249     protected void setSourceFormat(AudioSource source) {
250         source.setFormat(SampleFormat.float32, channels, samplesPerSecond, 32, 4*channels);
251     }
252 
253     /// handle format change: pass same format to sources
254     override protected void onFormatChanged() {
255         // already called from lock
256         foreach(source; _sources) {
257             setSourceFormat(source);
258         }
259     }
260 
261     private float[] _mixBuffer;
262     private float[] _sourceBuffer;
263     /// load data into buffer
264     override bool loadData(int frameCount, ubyte * buf, ref uint flags) {
265         lock();
266         scope(exit)unlock();
267         flags = 0;
268         // silence
269         if (_sources.length == 0) {
270             generateSilence(frameCount, buf);
271             flags |= AUDIO_SOURCE_SILENCE_FLAG;
272             return true;
273         }
274         int bufSize = frameCount * channels;
275         if (_mixBuffer.length < bufSize)
276             _mixBuffer.length = bufSize;
277         if (_sourceBuffer.length < bufSize)
278             _sourceBuffer.length = bufSize;
279         bool hasNonSilent = false;
280         uint tmpFlags = 0;
281         // load data from first source
282         _sources[0].loadData(frameCount, cast(ubyte*)_mixBuffer.ptr, tmpFlags);
283         if (!(tmpFlags & AUDIO_SOURCE_SILENCE_FLAG))
284             hasNonSilent = true;
285         // mix data from rest of sources
286         for(int i = 1; i < _sources.length; i++) {
287             tmpFlags = 0;
288             _sources[i].loadData(frameCount, cast(ubyte*)_sourceBuffer.ptr, tmpFlags);
289             if (!(tmpFlags & AUDIO_SOURCE_SILENCE_FLAG)) {
290                 hasNonSilent = true;
291                 for (int j = 0; j < bufSize; j++)
292                     _mixBuffer[j] += _sourceBuffer[j];
293             }
294         }
295         if (!hasNonSilent) {
296             // only silent sources present
297             generateSilence(frameCount, buf);
298             flags |= AUDIO_SOURCE_SILENCE_FLAG;
299             return true;
300         }
301         // put to output
302         if (sampleFormat == SampleFormat.float32) {
303             // copy as is
304             float * ptr = cast(float*)buf;
305             ptr[0 .. bufSize] = _mixBuffer[0 .. bufSize];
306         } else if (sampleFormat == SampleFormat.signed16) {
307             // convert to short
308             short * ptr = cast(short*)buf;
309             for (int i = 0; i < bufSize; i++) {
310                 float v = _mixBuffer.ptr[i];
311                 int sample = cast(int)(v * 32767.0f);
312                 limitShortRange(sample);
313                 ptr[i] = cast(short)sample;
314             }
315         } else if (sampleFormat == SampleFormat.signed32) {
316             // convert to short
317             int * ptr = cast(int*)buf;
318             for (int i = 0; i < bufSize; i++) {
319                 float v = _mixBuffer.ptr[i];
320                 if (v < -1)
321                     v = -1;
322                 else if (v > 1)
323                     v = 1;
324                 int sample = cast(int)(v * 0x7FFFFFFF);
325                 ptr[i] = sample;
326             }
327         } else {
328             // unsupported output format
329             generateSilence(frameCount, buf);
330         }
331         return true;
332     }
333 }