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 }