1 module polyplex.core.audio.music;
2 import polyplex.core.audio;
3 import polyplex.core.audio.effect;
4 import ppc.types.audio;
5 import openal;
6 import ppc.backend.cfile;
7 import polyplex.math;
8 import std.concurrency;
9 import std.stdio;
10 import polyplex.utils.logging;
11 import core.thread;
12 import core.time;
13 import core.sync.mutex;
14 
15 // This is set to true on application close, so that threads know to quit.
16 protected 	__gshared bool shouldStop;
17 protected 	__gshared int openMusicChannels;
18 
19 private void iHandlerLaunch() {
20 	synchronized {
21 		openMusicChannels++;
22 	}
23 }
24 
25 private void iHandlerStop() {
26 	synchronized {
27 		openMusicChannels--;
28 	}
29 }
30 
31 protected void MusicHandlerThread(Music iMus) {
32     try {
33 		iHandlerLaunch;
34 		int buffersProcessed;
35 		ALuint deqBuff;
36 		ALint state;
37 		byte[] streamBuffer = new byte[iMus.bufferSize];
38 
39 		size_t startBufferSize = Music.BufferSize;
40 
41         // Stop thread if requested.
42 	    while (iMus !is null && &shouldStop !is null && !shouldStop && !iMus.shouldStopInternal) {
43 
44             // Sleep needed to make the CPU NOT run at 100% at all times
45             Thread.sleep(10.msecs);
46 
47             // Check if we have any buffers to fill
48             alGetSourcei(iMus.source, AL_BUFFERS_PROCESSED, &buffersProcessed);
49             iMus.totalProcessed += buffersProcessed;
50             while(buffersProcessed > 0) {
51 
52                 // Find the ID of the buffer to fill
53                 deqBuff = 0;
54                 alSourceUnqueueBuffers(iMus.source, 1, &deqBuff);
55 
56 				// Resize buffers if needed
57 				if (startBufferSize != Music.BufferSize) {
58 					streamBuffer.length = Music.BufferSize;
59 					startBufferSize = Music.BufferSize;
60 					iMus.bufferSize = cast(int)Music.BufferSize;
61 				}
62 
63                 // Read audio from stream in
64                 size_t readData = iMus.stream.read(streamBuffer.ptr, iMus.bufferSize);
65                 if (readData == 0) {
66                     if (!iMus.looping) {
67 						iMus.playing = false;
68 						iHandlerStop;
69 						return;
70 					}
71 					iMus.stream.seek;
72 					readData = iMus.stream.read(streamBuffer.ptr, iMus.bufferSize);
73                 }
74 
75                 // Send to OpenAL
76                 alBufferData(deqBuff, iMus.fFormat, streamBuffer.ptr, cast(int)readData, cast(int)iMus.stream.info.bitrate);
77                 alSourceQueueBuffers(iMus.source, 1, &deqBuff);
78 
79                 buffersProcessed--;
80             }
81 
82             // OpenAL will stop the stream if it runs out of buffers
83             // Ensure that if OpenAL stops, we start it again.
84             alGetSourcei(iMus.source, AL_SOURCE_STATE, &state);
85             if (state != AL_PLAYING) {
86 				iMus.xruns++;
87                 Logger.Warn("Music buffer X-run!");
88                 alSourcePlay(iMus.source);
89             }
90 	    }   
91     } catch(Exception ex) {
92         Logger.Err("MusicHandlerException: {0}", ex.message);
93     } catch (Error err) {
94         Logger.Err("MusicHandlerError: {0}", err.message);
95 	}
96 	iHandlerStop;
97 }
98 
99 public class Music {
100 private:
101     // Internal thread-communication data.
102     size_t totalProcessed;
103     Thread musicThread;
104     bool shouldStopInternal;
105 
106     // Buffer
107     Audio stream;
108     ALuint[] buffer;
109     AudioRenderFormats fFormat;
110 
111     // Buffer sizing
112     int bufferSize;
113     int buffers;
114 
115     // Settings
116     bool looping;
117     bool paused;
118 	bool playing;
119 
120 	int xruns;
121 	
122 	// Effects & filters
123 	AudioEffect attachedEffect;
124 	AudioFilter attachedFilter;
125 
126 	void applyEffectsAndFilters() {
127 		ALuint efId = attachedEffect !is null ? attachedEffect.Id : AL_EFFECTSLOT_NULL;
128 		ALuint flId = attachedFilter !is null ? attachedFilter.Id : AL_FILTER_NULL;
129 
130 		Logger.Debug("Applying effect {0} and filter {1} on Music {2}...", efId, flId, source);
131 
132 		alSource3i(source, AL_AUXILIARY_SEND_FILTER, efId, 0, flId);
133 		alSourcei(source, AL_DIRECT_FILTER, flId);
134 		ErrCodes err = cast(ErrCodes)alGetError();
135 		
136 		import std.conv;
137 		if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to attach effect and/or filter to SoundEffect instance "~err.to!string);
138 	}
139 
140     // Source
141     ALuint source;
142 
143 public:
144 
145 	/// Changes the size of the buffers for music playback.
146 	/// Changes take effect immidiately
147 	static size_t BufferSize = 48_000;
148 
149 	/// Amount of buffers to provision per audio track
150 	/// Takes effect on new audio load.
151 	static size_t BufferCount = 16;
152 
153     /// Constructs a Music via an Audio source.
154     // TODO: Optimize enough so that we can have fewer buffers
155     this(Audio audio, AudioRenderFormats format = AudioRenderFormats.Auto) {
156         stream = audio;
157 		
158         // Select format if told to.
159 		if (format == AudioRenderFormats.Auto) {
160 			import std.conv;
161 			if (stream.info.channels == 1) fFormat = AudioRenderFormats.Mono16;
162 			else if (stream.info.channels == 2) fFormat = AudioRenderFormats.Stereo16;
163 			else throw new Exception("Unsupported amount of channels! " ~ stream.info.channels.to!string);
164 		}
165 
166         // Clear errors
167         alGetError();
168 
169         // Prepare buffer arrays
170         this.buffers = cast(int)BufferCount;
171         buffer = new ALuint[buffers];
172 
173         this.bufferSize = (cast(int)BufferSize)*stream.info.channels;
174 
175         alGenBuffers(buffers, buffer.ptr);
176 
177         // Prepare sources
178         alGenSources(1, &source);
179 
180         prestream();
181 
182         Logger.VerboseDebug("Created new Music! source={3} buffers={0} bufferSize={1} ids={2}", buffers, bufferSize, buffer, source);
183     }
184 
185     ~this() {
186         cleanup();
187     }
188 
189 	private void cleanup() {
190         // Cleanup procedure
191         alDeleteSources(1, &source);
192         alDeleteBuffers(buffers, buffer.ptr);
193 	}
194 
195     private void prestream() {
196 		byte[] streamBuffer = new byte[bufferSize];
197 
198         // Pre-read some data.
199         foreach (i; 0 .. buffers) {
200 
201 			// Read audio from stream in
202 			size_t read = stream.read(streamBuffer.ptr, bufferSize);
203 
204 			// Send to OpenAL
205 			alBufferData(buffer[i], this.fFormat, streamBuffer.ptr, cast(int)read, cast(int)stream.info.bitrate);
206 			alSourceQueueBuffers(source, 1, &buffer[i]);
207 
208         }
209     }
210 
211     // Spawns player thread.
212 	private void spawnHandler() {
213 		if (musicThread !is null && musicThread.isRunning) return;
214 
215 		musicThread = new Thread({
216 			MusicHandlerThread(this);
217 		});
218 		musicThread.start();
219 	}
220 
221     /// Play music
222     void Play(bool isLooping = false) {
223 		synchronized {
224 			if (musicThread is null || !musicThread.isRunning) spawnHandler();
225 
226 			this.Looping = isLooping;
227             alSourcePlay(source);
228 
229             paused = false;
230 			playing = true;
231 		}
232     }
233 
234     /// Stop playing music
235     /// Does nothing if music isn't playing.
236     void Stop() {
237 		synchronized {
238 			if (musicThread is null) return;
239 
240 			// Tell thread to die repeatedly, until it does.
241 			while(musicThread.isRunning) shouldStopInternal = true;
242 
243 			// Stop source and prepare for playing again
244 			alSourceStop(source);
245 			stream.seek();
246 			prestream();
247 
248 			playing = false;
249 		}
250     }
251 
252     /// Pause music
253 	void Pause() {
254         synchronized {
255 		    alSourcePause(source);
256             paused = true;
257 			playing = false;
258         }
259 	}
260 
261     /// Gets the current position in the music stream (in samples)
262     size_t Tell() {
263 		synchronized { 
264 			return stream.tell;
265 		}
266     }
267 
268     /// Seeks the music stream (in samples)
269     void Seek(size_t position = 0) {
270         synchronized {
271             if (position >= Length) position = Length-1;
272             // Stop stream
273             alSourceStop(source);
274 
275             // Unqueue everything
276             alSourceUnqueueBuffers(source, buffers, buffer.ptr);
277 
278             // Refill buffers
279             stream.seekSample(position);
280             prestream();
281 
282             // Resume
283             if (playing) alSourcePlay(source);
284         }
285     }
286 
287     /// Gets length of music (in samples)
288     size_t Length() {
289         return stream.info.pcmLength;
290     }
291 
292 	/// Get amount of unhandled XRuns that has happened.
293 	int XRuns() {
294 		synchronized {
295 			return xruns;
296 		}
297 	}
298 
299 	/// Mark XRuns as handled.
300 	void HandledXRun() {
301 		synchronized {
302 			xruns = 0;
303 		}
304 	}
305 
306     /// Gets wether the music is paused.
307     bool Paused() {
308         return paused;
309     }
310 
311 	/// Gets wether the music is playing
312 	bool Playing() {
313 		return playing;
314 	}
315 
316 	@property AudioEffect Effect() {
317 		return this.attachedEffect;
318 	}
319 
320 	@property void Effect(AudioEffect effect) {
321 		attachedEffect = effect;
322 		applyEffectsAndFilters();
323 	}
324 
325 	@property AudioFilter Filter() {
326 		return this.attachedFilter;
327 	}
328 
329 	@property void Filter(AudioFilter filter) {
330 		attachedFilter = filter;
331 		applyEffectsAndFilters();
332 	}
333 
334 	@property bool Looping() {
335 		return looping;
336 	}
337 	@property void Looping(bool val) { looping = val; }
338 
339 	@property int ByteOffset() {
340 		int v = 0;
341 		alGetSourcei(source, AL_BYTE_OFFSET, &v);
342 		return v;
343 	}
344 
345 	@property int SecondOffset() {
346 		int v = 0;
347 		alGetSourcei(source, AL_SEC_OFFSET, &v);
348 		return v;
349 	}
350 
351 	@property int SampleOffset() {
352 		int v = 0;
353 		alGetSourcei(source, AL_SAMPLE_OFFSET, &v);
354 		return v;
355 	}
356 
357 	/*
358 		PITCH
359 	*/
360 	@property float Pitch() {
361 		float v = 0f;
362 		alGetSourcef(source, AL_PITCH, &v);
363 		return v;
364 	}
365 	@property void Pitch(float val) { alSourcef(source, AL_PITCH, val); }
366 
367 	/*
368 		GAIN
369 	*/
370 	@property float Gain() {
371 		float v = 0f;
372 		alGetSourcef(source, AL_GAIN, &v);
373 		return v;
374 	}
375 	@property void Gain(float val) { alSourcef(source, AL_GAIN, val); }
376 
377 	/*
378 		MIN GAIN
379 	*/
380 	@property float MinGain() {
381 		float v = 0f;
382 		alGetSourcef(source, AL_MIN_GAIN, &v);
383 		return v;
384 	}
385 	@property void MinGain(float val) { alSourcef(source, AL_MIN_GAIN, val); }
386 
387 	/*
388 		MAX GAIN
389 	*/
390 	@property float MaxGain() {
391 		float v = 0f;
392 		alGetSourcef(source, AL_MAX_GAIN, &v);
393 		return v;
394 	}
395 	@property void MaxGain(float val) { alSourcef(source, AL_MAX_GAIN, val); }
396 
397 	/*
398 		MAX DISTANCE
399 	*/
400 	@property float MaxDistance() {
401 		float v = 0f;
402 		alGetSourcef(source, AL_MAX_DISTANCE, &v);
403 		return v;
404 	}
405 	@property void MaxDistance(float val) { alSourcef(source, AL_MAX_DISTANCE, val); }
406 
407 	/*
408 		ROLLOFF FACTOR
409 	*/
410 	@property float RolloffFactor() {
411 		float v = 0f;
412 		alGetSourcef(source, AL_ROLLOFF_FACTOR, &v);
413 		return v;
414 	}
415 	@property void RolloffFactor(float val) { alSourcef(source, AL_ROLLOFF_FACTOR, val); }
416 
417 	/*
418 		CONE OUTER GAIN
419 	*/
420 	@property float ConeOuterGain() {
421 		float v = 0f;
422 		alGetSourcef(source, AL_CONE_OUTER_GAIN, &v);
423 		return v;
424 	}
425 	@property void ConeOuterGain(float val) { alSourcef(source, AL_CONE_OUTER_GAIN, val); }
426 
427 	/*
428 		CONE INNER ANGLE
429 	*/
430 	@property float ConeInnerAngle() {
431 		float v = 0f;
432 		alGetSourcef(source, AL_CONE_INNER_ANGLE, &v);
433 		return v;
434 	}
435 	@property void ConeInnerAngle(float val) { alSourcef(source, AL_CONE_INNER_ANGLE, val); }
436 
437 	/*
438 		CONE OUTER ANGLE
439 	*/
440 	@property float ConeOuterAngle() {
441 		float v = 0f;
442 		alGetSourcef(source, AL_CONE_OUTER_ANGLE, &v);
443 		return v;
444 	}
445 	@property void ConeOuterAngle(float val) { alSourcef(source, AL_CONE_OUTER_ANGLE, val); }
446 
447 	/*
448 		REFERENCE DISTANCE
449 	*/
450 	@property float ReferenceDistance() {
451 		float v = 0f;
452 		alGetSourcef(source, AL_REFERENCE_DISTANCE, &v);
453 		return v;
454 	}
455 	@property void ReferenceDistance(float val) { alSourcef(source, AL_REFERENCE_DISTANCE, val); }
456 
457 	/*
458 		POSITION
459 	*/
460 	@property Vector3 Position() {
461 		Vector3 v = 0f;
462 		alGetSourcefv(source, AL_POSITION, v.ptr);
463 		return v;
464 	}
465 	@property void Position(Vector3 val) { alSourcefv(source, AL_POSITION, val.ptr); }
466 
467 	/*
468 		VELOCITY
469 	*/
470 	@property Vector3 Velocity() {
471 		Vector3 v = 0f;
472 		alGetSourcefv(source, AL_VELOCITY, v.ptr);
473 		return v;
474 	}
475 	@property void Velocity(Vector3 val) { alSourcefv(source, AL_VELOCITY, val.ptr); }
476 
477 	/*
478 		DIRECTION
479 	*/
480 	@property Vector3 Direction() {
481 		Vector3 v = 0f;
482 		alGetSourcefv(source, AL_DIRECTION, v.ptr);
483 		return v;
484 	}
485 	@property void Direction(Vector3 val) { alSourcefv(source, AL_DIRECTION, val.ptr); }
486 }