1 /* 2 __ 3 / _| 4 __ _ _ _ _ __ ___ _ __ __ _ | |_ ___ ___ ___ 5 / _` | | | | '__/ _ \| '__/ _` | | _/ _ \/ __/ __| 6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \ 7 \__,_|\__,_|_| \___/|_| \__,_| |_| \___/|___/___/ 8 9 Copyright (C) 2018-2019 Aurora Free Open Source Software. 10 11 This file is part of the Aurora Free Open Source Software. This 12 organization promote free and open source software that you can 13 redistribute and/or modify under the terms of the GNU Lesser General 14 Public License Version 3 as published by the Free Software Foundation or 15 (at your option) any later version approved by the Aurora Free Open Source 16 Software Organization. The license is available in the package root path 17 as 'LICENSE' file. Please review the following information to ensure the 18 GNU Lesser General Public License version 3 requirements will be met: 19 https://www.gnu.org/licenses/lgpl.html . 20 21 Alternatively, this file may be used under the terms of the GNU General 22 Public License version 3 or later as published by the Free Software 23 Foundation. Please review the following information to ensure the GNU 24 General Public License requirements will be met: 25 http://www.gnu.org/licenses/gpl-3.0.html. 26 27 NOTE: All products, services or anything associated to trademarks and 28 service marks used or referenced on this file are the property of their 29 respective companies/owners or its subsidiaries. Other names and brands 30 may be claimed as the property of others. 31 32 For more info about intellectual property visit: aurorafoss.org or 33 directly send an email to: contact (at) aurorafoss.org . 34 */ 35 36 module aurorafw.audio.output; 37 38 import std.conv : text, to; 39 import std..string : toStringz; 40 import std.math : round, rint; 41 import std.algorithm : max; 42 import core.thread : Thread; 43 import core.time: Duration, dur; 44 45 import aurorafw.stdx.exception; 46 import aurorafw.core.logger; 47 import aurorafw.audio.backend : AudioDevice, catchSOUNDIOProblem, catchSNDFILEProblem, AudioBackend; 48 import aurorafw.audio.utils : AudioInfo; 49 import riverd.sndfile; 50 import riverd.soundio; 51 52 enum AudioPlayMode : byte { 53 Once, 54 Loop 55 } 56 57 class AudioFileNotFoundException : Throwable { 58 this(string file) { 59 super("The specified audio file \"" ~ file ~ "\" couldn't be found/read!"); 60 } 61 } 62 63 extern(C) void audioOutputCallback(SoundIoOutStream* stream, int minFrames, int maxFrames) { 64 //TODO: Need implementation 65 66 // Gets the output buffer (it's of type float32NE), and 67 // the audioStream and audioInfo 68 // DEBUG 69 70 AudioOStream audioStream = cast(AudioOStream)(stream.userdata); 71 AudioInfo audioInfo = audioStream.audioInfo; 72 73 int framesLeft = maxFrames; 74 75 // If the stream is stopped, exit the callback 76 if(audioStream.isStopped()) { 77 trace("Callback stopped at beginning..."); 78 return; 79 } 80 81 // Fills the buffer to it's fullest 82 while(framesLeft > 0) { 83 // Gets the number of frames it needs to read 84 //int framesToRead = framesLeft; 85 int framesToRead = 256; 86 SoundIoChannelArea* area; 87 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_begin_write(stream, &area, &framesToRead)); 88 89 // Reads the frames 90 int framesRead; 91 do { 92 // Fill the data using either buffer or stream 93 if(audioStream._buffer.length > 0) { // Buffered 94 framesRead = (framesToRead + audioStream._streamPosFrame) > audioInfo.frames 95 ? audioInfo.frames - audioStream._streamPosFrame 96 : framesToRead; 97 98 for(int f = 0; f < framesRead; f++) { 99 for(ubyte c = 0; c < audioInfo.channels; c++) { 100 float* output = cast(float*)(area[c].ptr + area[c].step * f); 101 *output = audioStream._buffer[audioStream._streamPosFrame 102 * audioInfo.channels + (f * audioInfo.channels 103 + c)]; 104 } 105 } 106 } else { // Streaming 107 float* output = cast(float*)(area.ptr); 108 framesRead = cast(int)sf_readf_float(audioInfo._sndFile, output, framesToRead); 109 } 110 111 // Updates the value of frames read 112 framesToRead -= framesRead; 113 framesLeft -= framesRead; 114 audioStream._streamPosFrame += framesRead; 115 116 // If it reached EOF and is in looping mode, restart accordingly 117 if(framesToRead > 0 && audioStream.audioPlayMode == AudioPlayMode.Loop) { 118 audioStream._streamPosFrame = 0; 119 audioStream._loops++; 120 // If streaming reset the seek pointer to start of file 121 if(audioStream._buffer.length == 0) 122 sf_seek(audioInfo._sndFile, 0, SF_SEEK_SET); 123 } 124 } while(framesToRead > 0 && audioStream.audioPlayMode == AudioPlayMode.Loop); 125 126 debug trace("Frames to read: ", framesToRead, "\tFrames read: ", framesRead, "\tFrames left: ", framesLeft); 127 128 // If the read frames didn't fill the buffer to read, it reached EOF 129 if(framesToRead > 0 && audioStream.audioPlayMode 130 == AudioPlayMode.Once) { 131 debug trace("No more frames to read, filling the rest with zero..."); 132 for(int f = 0; f < framesLeft; f++) { 133 for(ubyte c = 0; c < audioInfo.channels; c++) { 134 float* output = cast(float*)(area[c].ptr + area[c].step * (f + framesRead)); 135 *output = 0; 136 } 137 } 138 //audioStream.stop(); 139 framesLeft = 0; 140 break; 141 } 142 143 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_end_write(stream)); 144 } 145 trace("Exited while loop!"); 146 } 147 148 extern(C) void debugCallback(SoundIoOutStream* stream, int minFrames, int maxFrames) { 149 ubyte left_ear = 0; 150 ubyte right_ear = 0; 151 152 SoundIoChannelArea* area; 153 154 int desiredFrames = maxFrames; 155 156 while(desiredFrames > 0) { 157 int frames = 256; 158 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_begin_write(stream, &area, &frames)); 159 160 for(int i = 0; i < frames; i++) { 161 *(area[0].ptr) = left_ear; 162 area[0].ptr += area[0].step; 163 *(area[1].ptr) = right_ear; 164 area[1].ptr += area[1].step; 165 166 left_ear += 1; 167 if(left_ear >= 255) 168 left_ear -= 255; 169 170 right_ear += 3; 171 if(right_ear >= 255) 172 right_ear -= 255; 173 } 174 175 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_end_write(stream)); 176 177 desiredFrames -= frames; 178 } 179 } 180 181 extern(C) void underFlowCallback(SoundIoOutStream* stream) { 182 trace("Underflow ocurred!"); 183 } 184 185 class AudioOStream { 186 this() { 187 audioInfo = new AudioInfo(); 188 trace("Debug mode activated for AudioStream instance"); 189 190 AudioDevice device = new AudioDevice(); 191 _stream = soundio_outstream_create(device.device); 192 193 _stream.write_callback = &debugCallback; 194 _stream.sample_rate = device.sampleRate; 195 if(soundio_device_supports_format(device.device, SoundIoFormat.SoundIoFormatU8)) 196 _stream.format = SoundIoFormat.SoundIoFormatU8; 197 _stream.name = "AuroraFW Application".toStringz; 198 199 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_open(_stream)); 200 } 201 202 this(immutable string path, immutable bool buffered) { 203 audioInfo = new AudioInfo(); 204 205 audioInfo._sndFile = sf_open(path.toStringz, SFM_READ, audioInfo._sndInfo); 206 207 // If the soundFile is null, it means there was no audio file 208 if(!audioInfo._sndFile) 209 throw new AudioFileNotFoundException(path); 210 211 // If the audio should be buffered, do so 212 if(buffered) { 213 trace("Buffering the audio... (Total frames: " ~ text(audioInfo.frames * audioInfo.channels) ~ ")"); 214 _buffer = new float[audioInfo.frames * audioInfo.channels]; 215 sf_readf_float(audioInfo._sndFile, _buffer.ptr, audioInfo.frames); 216 trace("Buffering complete."); 217 } 218 219 AudioDevice device = new AudioDevice(); 220 _stream = soundio_outstream_create(device.device); 221 222 _stream.write_callback = &audioOutputCallback; 223 _stream.sample_rate = device.sampleRate; 224 _stream.name = "AuroraFW Application".toStringz; 225 if(soundio_device_supports_format(device.device, SoundIoFormat.SoundIoFormatU32LE)) 226 _stream.format = SoundIoFormat.SoundIoFormatU32LE; 227 _stream.userdata = cast(void*)this; 228 229 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_open(_stream)); 230 } 231 232 ~this() { 233 soundio_outstream_destroy(_stream); 234 } 235 236 void play() { 237 playing = true; 238 //sf_seek(audioInfo._sndFile, _streamPosFrame, SF_SEEK_SET); 239 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_start(_stream)); 240 } 241 242 void pause() { 243 playing = false; 244 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_pause(_stream, true)); 245 } 246 247 void stop() { 248 _streamPosFrame = 0; 249 250 playing = false; 251 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_pause(_stream, true)); 252 //Thread.sleep(dur!("msecs")(cast(long)rint(_stream.software_latency * 1000))); 253 } 254 255 void callbackStop() { 256 trace("Being called!"); 257 callbackStopped = true; 258 } 259 260 bool isPlaying() { 261 return playing; 262 } 263 264 bool isPaused() { 265 return !playing && _streamPosFrame != 0; 266 } 267 268 bool isStopped() { 269 debug trace("!playing: ", text(!playing), "\t_streamPosFrame: ", _streamPosFrame); 270 return !playing && _streamPosFrame == 0; 271 } 272 273 bool isCallbackStopped() { 274 trace(text(callbackStopped)); 275 return callbackStopped; 276 } 277 278 void setStreamPos(double pos) { 279 setStreamPosFrame(to!uint(round(pos * audioInfo.sampleRate) * audioInfo.channels)); 280 } 281 282 void setStreamPosFrame(uint pos) { 283 _streamPosFrame = pos; 284 } 285 286 float getNumLoops() { 287 return _loops; 288 } 289 290 float getCpuLoad() { 291 //TODO: Need implementation 292 throw new NotImplementedException("Not yet implemented!"); 293 // return soundio_get_cpu_load(...); 294 } 295 296 AudioPlayMode audioPlayMode; 297 AudioInfo audioInfo; 298 299 float volume = 1; 300 //TODO: Implement pitch 301 float pitch = 1; 302 303 private: 304 SoundIoOutStream* _stream; 305 float[] _buffer; 306 int _streamPosFrame = 0; 307 ubyte _loops; 308 bool playing, callbackStopped = false; 309 }