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 }