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.core.debugmanager; 46 import aurorafw.core.logger; 47 import aurorafw.audio.backend : AudioDevice, catchSOUNDIOProblem, catchSNDFILEProblem, AudioBackend; 48 import aurorafw.audio.utils : AudioInfo; 49 import aurorafw.audio.sndfile; 50 import aurorafw.audio.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 pragma(msg, "TODO: Cleanup this method as much as possible"); 65 // Gets the output buffer (it's of type float32NE), and 66 // the audioStream and audioInfo 67 // DEBUG 68 69 AudioOStream audioStream = cast(AudioOStream)(stream.userdata); 70 AudioInfo audioInfo = audioStream.audioInfo; 71 72 int framesLeft = maxFrames; 73 74 // If the stream is stopped, exit the callback 75 if(audioStream.isStopped()) { 76 trace("Callback stopped at beginning..."); 77 return; 78 } 79 80 // Fills the buffer to it's fullest 81 while(framesLeft > 0) { 82 // Gets the number of frames it needs to read 83 //int framesToRead = framesLeft; 84 int framesToRead = 256; 85 SoundIoChannelArea* area; 86 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_begin_write(stream, &area, &framesToRead)); 87 88 // Reads the frames 89 int framesRead; 90 do { 91 // Fill the data using either buffer or stream 92 if(audioStream._buffer.length > 0) { // Buffered 93 framesRead = (framesToRead + audioStream._streamPosFrame) > audioInfo.frames 94 ? audioInfo.frames - audioStream._streamPosFrame 95 : framesToRead; 96 97 for(int f = 0; f < framesRead; f++) { 98 for(ubyte c = 0; c < audioInfo.channels; c++) { 99 float* output = cast(float*)(area[c].ptr + area[c].step * f); 100 *output = audioStream._buffer[audioStream._streamPosFrame 101 * audioInfo.channels + (f * audioInfo.channels 102 + c)]; 103 } 104 } 105 } else { // Streaming 106 float* output = cast(float*)(area.ptr); 107 framesRead = cast(int)sf_readf_float(audioInfo._sndFile, output, framesToRead); 108 } 109 110 // Updates the value of frames read 111 framesToRead -= framesRead; 112 framesLeft -= framesRead; 113 audioStream._streamPosFrame += framesRead; 114 115 // If it reached EOF and is in looping mode, restart accordingly 116 if(framesToRead > 0 && audioStream.audioPlayMode == AudioPlayMode.Loop) { 117 audioStream._streamPosFrame = 0; 118 audioStream._loops++; 119 // If streaming reset the seek pointer to start of file 120 if(audioStream._buffer.length == 0) 121 sf_seek(audioInfo._sndFile, 0, SF_SEEK_SET); 122 } 123 } while(framesToRead > 0 && audioStream.audioPlayMode == AudioPlayMode.Loop); 124 125 debug trace("Frames to read: ", framesToRead, "\tFrames read: ", framesRead, "\tFrames left: ", framesLeft); 126 127 // If the read frames didn't fill the buffer to read, it reached EOF 128 if(framesToRead > 0 && audioStream.audioPlayMode 129 == AudioPlayMode.Once) { 130 debug trace("No more frames to read, filling the rest with zero..."); 131 for(int f = 0; f < framesLeft; f++) { 132 for(ubyte c = 0; c < audioInfo.channels; c++) { 133 float* output = cast(float*)(area[c].ptr + area[c].step * (f + framesRead)); 134 *output = 0; 135 } 136 } 137 //audioStream.stop(); 138 framesLeft = 0; 139 break; 140 } 141 142 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_end_write(stream)); 143 } 144 trace("Exited while loop!"); 145 } 146 147 extern(C) void debugCallback(SoundIoOutStream* stream, int minFrames, int maxFrames) { 148 ubyte left_ear = 0; 149 ubyte right_ear = 0; 150 151 SoundIoChannelArea* area; 152 153 int desiredFrames = maxFrames; 154 155 while(desiredFrames > 0) { 156 int frames = 256; 157 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_begin_write(stream, &area, &frames)); 158 159 for(int i = 0; i < frames; i++) { 160 *(area[0].ptr) = left_ear; 161 area[0].ptr += area[0].step; 162 *(area[1].ptr) = right_ear; 163 area[1].ptr += area[1].step; 164 165 left_ear += 1; 166 if(left_ear >= 255) 167 left_ear -= 255; 168 169 right_ear += 3; 170 if(right_ear >= 255) 171 right_ear -= 255; 172 } 173 174 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_end_write(stream)); 175 176 desiredFrames -= frames; 177 } 178 } 179 180 extern(C) void underFlowCallback(SoundIoOutStream* stream) { 181 trace("Underflow ocurred!"); 182 } 183 184 class AudioOStream { 185 this() { 186 audioInfo = new AudioInfo(); 187 trace("Debug mode activated for AudioStream instance"); 188 189 AudioDevice device = new AudioDevice(); 190 _stream = soundio_outstream_create(device.device); 191 192 _stream.write_callback = &debugCallback; 193 _stream.sample_rate = device.sampleRate; 194 if(soundio_device_supports_format(device.device, SoundIoFormat.SoundIoFormatU8)) 195 _stream.format = SoundIoFormat.SoundIoFormatU8; 196 _stream.name = "AuroraFW Application".toStringz; 197 198 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_open(_stream)); 199 } 200 201 this(immutable string path, immutable bool buffered) { 202 audioInfo = new AudioInfo(); 203 204 audioInfo._sndFile = sf_open(path.toStringz, SFM_READ, audioInfo._sndInfo); 205 206 // If the soundFile is null, it means there was no audio file 207 if(!audioInfo._sndFile) 208 throw new AudioFileNotFoundException(path); 209 210 // If the audio should be buffered, do so 211 if(buffered) { 212 trace("Buffering the audio... (Total frames: " ~ text(audioInfo.frames * audioInfo.channels) ~ ")"); 213 _buffer = new float[audioInfo.frames * audioInfo.channels]; 214 sf_readf_float(audioInfo._sndFile, _buffer.ptr, audioInfo.frames); 215 trace("Buffering complete."); 216 } 217 218 AudioDevice device = new AudioDevice(); 219 _stream = soundio_outstream_create(device.device); 220 221 _stream.write_callback = &audioOutputCallback; 222 _stream.sample_rate = device.sampleRate; 223 _stream.name = "AuroraFW Application".toStringz; 224 if(soundio_device_supports_format(device.device, SoundIoFormat.SoundIoFormatU32LE)) 225 _stream.format = SoundIoFormat.SoundIoFormatU32LE; 226 _stream.userdata = cast(void*)this; 227 228 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_open(_stream)); 229 } 230 231 ~this() { 232 soundio_outstream_destroy(_stream); 233 } 234 235 void play() { 236 playing = true; 237 //sf_seek(audioInfo._sndFile, _streamPosFrame, SF_SEEK_SET); 238 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_start(_stream)); 239 } 240 241 void pause() { 242 playing = false; 243 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_pause(_stream, true)); 244 } 245 246 void stop() { 247 _streamPosFrame = 0; 248 249 playing = false; 250 catchSOUNDIOProblem(cast(SoundIoError)soundio_outstream_pause(_stream, true)); 251 //Thread.sleep(dur!("msecs")(cast(long)rint(_stream.software_latency * 1000))); 252 } 253 254 void callbackStop() { 255 trace("Being called!"); 256 callbackStopped = true; 257 } 258 259 bool isPlaying() { 260 return playing; 261 } 262 263 bool isPaused() { 264 return !playing && _streamPosFrame != 0; 265 } 266 267 bool isStopped() { 268 debug trace("!playing: ", text(!playing), "\t_streamPosFrame: ", _streamPosFrame); 269 return !playing && _streamPosFrame == 0; 270 } 271 272 bool isCallbackStopped() { 273 trace(text(callbackStopped)); 274 return callbackStopped; 275 } 276 277 void setStreamPos(double pos) { 278 setStreamPosFrame(to!uint(round(pos * audioInfo.sampleRate) * audioInfo.channels)); 279 } 280 281 void setStreamPosFrame(uint pos) { 282 _streamPosFrame = pos; 283 } 284 285 float getNumLoops() { 286 return _loops; 287 } 288 289 float getCpuLoad() { 290 pragma(msg, debugMsgPrefix, "TODO: Implement getCpuLoad()"); 291 // return soundio_get_cpu_load(...); 292 return 0; 293 } 294 295 AudioPlayMode audioPlayMode; 296 AudioInfo audioInfo; 297 298 float volume = 1; 299 pragma(msg, debugMsgPrefix, "TODO: Implement pitch"); 300 float pitch = 1; 301 302 private: 303 SoundIoOutStream* _stream; 304 float[] _buffer; 305 int _streamPosFrame = 0; 306 ubyte _loops; 307 bool playing, callbackStopped = false; 308 }