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 }