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