1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  * 
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  * 
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  * 
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  * 
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  * 
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Audio.Sound;
25 
26 private {
27 	import std.algorithm : endsWith;
28 	import std.exception : enforce;
29 	
30 	import derelict.openal.al;
31 	
32 	import Dgame.Internal.Log;
33 	import Dgame.Math.Vector3;
34 	import Dgame.Audio.SoundFile;
35 	import Dgame.Audio.VorbisFile;
36 	import Dgame.Audio.WaveFile;
37 }
38 
39 @safe
40 private char toLower(char ch) pure nothrow { 
41 	return ch | 32; 
42 }
43 
44 @safe
45 private string toLower(string str) pure nothrow {
46 	char[] s = new char[str.length];
47 	
48 	for (uint i = 0; i < str.length; i++) {
49 		s[i] = toLower(str[i]);
50 	}
51 	
52 	return s;
53 }
54 
55 /**
56  * Channel struct which stores the channel information.
57  */
58 struct Channel {
59 	/**
60 	 * The channel type. Mono or Stereo.
61 	 */
62 	enum Type : ubyte {
63 		Mono,	/** Channel type is Mono */
64 		Stereo	/** Channel type is Stereo */
65 	}
66 
67 	ubyte bits;	/** How many bits has this channel */
68 	Type type;	/** Which type is this channel */
69 	
70 	/**
71 	 * CTor
72 	 */
73 	this(ubyte bits, Type type) {
74 		this.bits = bits;
75 		this.type = type;
76 	}
77 	
78 	/**
79 	 * Short set function.
80 	 *
81 	 * Example:
82 	 * ---
83 	 * Channel ch;
84 	 * ch(8, Channel.Type.Stereo); // set bits and type for this channel.
85 	 * ---
86 	 */
87 	void opCall(ubyte bits, Type type) {
88 		this.bits = bits;
89 		this.type = type;
90 	}
91 }
92 
93 private struct ALChunk {
94 	ALuint source;
95 	ALuint buffer;
96 	
97 	/**
98 	 * Create and initialize the buffers
99 	 */
100 	void init() {
101 		alGenBuffers(1, &this.buffer);
102 		alGenSources(1, &this.source);
103 	}
104 	
105 	/**
106 	 * Free / Release the source and buffer of the Sound
107 	 */
108 	void free() {
109 		if (this.source && this.buffer) {
110 			alDeleteSources(1, &this.source);
111 			alDeleteBuffers(1, &this.buffer);
112 		}
113 	}
114 	
115 	@disable
116 	this(this);
117 }
118 
119 private ALChunk*[] _ALFinalizer;
120 
121 static ~this() {
122 	debug Log.info("Finalize Sound (%d)", _ALFinalizer.length);
123 	
124 	for (size_t i = 0; i < _ALFinalizer.length; i++) {
125 		if (_ALFinalizer[i]) {
126 			debug Log.info(" -> Sound finalized: %d", i);
127 			_ALFinalizer[i].free();
128 		}
129 	}
130 	
131 	_ALFinalizer = null;
132 	
133 	debug Log.info(" >> Sound Finalized");
134 }
135 
136 /**
137  * Sound represents the functionality to manipulate loaded sounds.
138  * That means with this class you can get informations about the sound or
139  * you can play, stop or pause it.
140  *
141  * Author: rschuett
142  */
143 class Sound {
144 	/**
145 	 * Represents the current status.
146 	 */
147 	enum Status : ubyte {
148 		None,	 /** No information */
149 		Stopped, /** Sound is stopped */
150 		Paused,	 /** Sound is paused */
151 		Playing  /** Sound is playing */
152 	}
153 
154 private:
155 	ALChunk _alChunk;
156 	ALenum _format;
157 	
158 	uint _frequency;
159 	float _volume;
160 	bool _looping;
161 	
162 	Vector3f _sourcePos;
163 	Vector3f _sourceVel;
164 	
165 	Status _status;
166 	Channel _channel;
167 	
168 	BaseSoundFile _soundfile;
169 	
170 private:
171 	static Sound[string] _soundInstances;
172 	
173 public:
174 final:
175 	/**
176 	 * CTor
177 	 */
178 	this() {
179 		this._alChunk.init();
180 		this._status = Status.None;
181 		
182 		_ALFinalizer ~= &this._alChunk;
183 	}
184 	
185 	/**
186 	 * CTor
187 	 */
188 	this(BaseSoundFile soundfile) {
189 		this();
190 		this.loadFromFile(soundfile);
191 	}
192 	
193 	/**
194 	 * CTor
195 	 */
196 	this(string filename) {
197 		this();
198 		this.loadFromFile(filename);
199 	}
200 	
201 	/**
202 	 * Free / Release the source and buffer of the Sound
203 	 */
204 	void free() {
205 		this._alChunk.free();
206 	}
207 	
208 	/**
209 	 * Load a soundfile and stores the sound object in a static field.
210 	 * Then returns the sound object.
211 	 * If you try to load the same file on more time, you get the same sound object as before.
212 	 */
213 	static Sound loadOnce(BaseSoundFile soundfile) in {
214 		assert(soundfile !is null, "Soundfile is null.");
215 	} body {
216 		return Sound.loadOnce(soundfile.getFilename());
217 	}
218 	
219 	/**
220 	 * Load a soundfile from his path and stores the sound object in a static field.
221 	 * Then returns the sound object.
222 	 * If you try to load the same file on more time, you get the same sound object as before.
223 	 */
224 	static Sound loadOnce(string filename) {
225 		if (Sound* s = filename in _soundInstances)
226 			return *s;
227 		
228 		Sound s = new Sound(filename);
229 		_soundInstances[filename] = s;
230 		
231 		return s;
232 	}
233 	
234 	/**
235 	 * Load from a soundfile.
236 	 *
237 	 * Example:
238 	 * ---
239 	 * Sound s1 = new Sound();
240 	 * s1.loadFromFile(new VorbisFile("samples/audio/orchestral.ogg"));
241 	 * ---
242 	 *
243 	 * See: BaseSoundFile
244 	 * See: VorbisFile
245 	 * See: WaveFile
246 	 */
247 	void loadFromFile(BaseSoundFile soundfile) in {
248 		assert(soundfile !is null, "Soundfile is null.");
249 	} body {
250 		const SoundFile* sFile = &soundfile.getData();
251 		this._soundfile = soundfile;
252 		
253 		/// Load
254 		Channel ch = void;
255 		switch (sFile.bits) {
256 			case 8:
257 				if (sFile.channels == 1)
258 					ch(8, Channel.Type.Mono);
259 				else
260 					ch(8, Channel.Type.Stereo);
261 				break;
262 				
263 			case 16:
264 				if (sFile.channels == 1)
265 					ch(16, Channel.Type.Mono);
266 				else
267 					ch(16, Channel.Type.Stereo);
268 				break;
269 				
270 			default: 
271 				debug Log.info("Bits: %d", sFile.bits);
272 				Log.error("Switch error.");
273 		}
274 		
275 		this.loadFromMemory(soundfile.getBuffer().ptr, sFile.dataSize, sFile.rate, ch);
276 	}
277 	
278 	/**
279 	 * Load a soundfile from his path.
280 	 *
281 	 * Example:
282 	 * ---
283 	 * Sound s1 = new Sound();
284 	 * if (s1.loadFromFile("samples/audio/orchestral.ogg") is null)
285 	 *	throw new Exception("Could not load file.");
286 	 * ---
287 	 *
288 	 * See: BaseSoundFile
289 	 * See: VorbisFile
290 	 * See: WaveFile
291 	 */
292 	BaseSoundFile loadFromFile(string filename) {
293 		enforce(exists(filename), "Soundfile " ~ filename ~ " does not exist.");
294 
295 		BaseSoundFile sFile;
296 		
297 	Lagain:
298 		if (filename.endsWith(".ogg") || filename.endsWith(".vorbis"))
299 			sFile = new VorbisFile(filename);
300 		else if (filename.endsWith(".wav") || filename.endsWith(".wave"))
301 			sFile = new WaveFile(filename);
302 		else {
303 			const string lower = toLower(filename); // for e.g. *.WAVE
304 			if (lower != filename) {
305 				filename = lower;
306 				goto Lagain;
307 			}
308 		}
309 		
310 		if (sFile !is null)
311 			this.loadFromFile(sFile);
312 		
313 		return sFile;
314 	}
315 	
316 	/**
317 	 * Load from memory.
318 	 */
319 	void loadFromMemory(const void* buffer, uint dataSize, uint frequency, ref const Channel ch) in {
320 		assert(buffer !is null, "Buffer is null.");
321 	} body {
322 		switch (ch.bits) {
323 			case 8:
324 				if (ch.type == Channel.Type.Mono)
325 					this._format = AL_FORMAT_MONO8;
326 				else
327 					this._format = AL_FORMAT_STEREO8;
328 				break;
329 
330 			case 16:
331 				if (ch.type == Channel.Type.Mono)
332 					this._format = AL_FORMAT_MONO16;
333 				else
334 					this._format = AL_FORMAT_STEREO16;
335 				break;
336 			default: Log.error("Switch error.");
337 		}
338 		
339 		this._frequency = frequency;
340 		this._channel = ch;
341 		
342 		alBufferData(this._alChunk.buffer, this._format, buffer, dataSize, this._frequency);
343 		
344 		this._sourcePos = Vector3f(0, 0, 0);
345 		this._sourceVel = Vector3f(0, 0, 0);
346 		
347 		this._looping = false;
348 		this._volume  = 1f;
349 		this._status  = Status.None;
350 		
351 		const float[3] pos = this._sourcePos.asArray();
352 		const float[3] vel = this._sourceVel.asArray();
353 		
354 		// Source
355 		alSourcei(this._alChunk.source, AL_BUFFER, this._alChunk.buffer);
356 		alSourcef(this._alChunk.source, AL_PITCH, 1.0);
357 		alSourcef(this._alChunk.source, AL_GAIN, this._volume);
358 		alSourcefv(this._alChunk.source, AL_POSITION, &pos[0]);
359 		alSourcefv(this._alChunk.source, AL_VELOCITY, &vel[0]);
360 		alSourcei(this._alChunk.source, AL_LOOPING, this._looping);
361 	}
362 	
363 	/**
364 	 * Returns the interal Soundfile which contains the filename, the music type and the length in seconds.
365 	 */
366 	BaseSoundFile getSoundFile() pure nothrow {
367 		return this._soundfile;
368 	}
369 	
370 	/**
371 	 * Returns the current filename.
372 	 */
373 	string getFilename() const pure nothrow {
374 		return this._soundfile !is null ? this._soundfile.getFilename() : null;
375 	}
376 	
377 	/**
378 	 * Returns the length in seconds.
379 	 */
380 	float getLength() const pure nothrow {
381 		return this._soundfile !is null ? this._soundfile.getLength() : 0f;
382 	}
383 	
384 	/**
385 	 * Returns the music type.
386 	 */
387 	MusicType getType() const pure nothrow {
388 		return this._soundfile !is null ? this._soundfile.getType() : MusicType.None;
389 	}
390 	
391 	/**
392 	 * Returns the Format.
393 	 */
394 	ALenum getFormat() const pure nothrow {
395 		return this._format;
396 	}
397 	
398 	/**
399 	 * Returns the current status.
400 	 *
401 	 * See: Status enum
402 	 */
403 	Status getStatus() const pure nothrow {
404 		return this._status;
405 	}
406 	
407 	/**
408 	 * Returns the current Channel.
409 	 *
410 	 * See: Channel struct
411 	 */
412 	ref const(Channel) getChannel() const pure nothrow {
413 		return this._channel;
414 	}
415 	
416 	/**
417 	 * Returns the current frequency.
418 	 */
419 	uint getFreqeuency() const pure nothrow {
420 		return this._frequency;
421 	}
422 	
423 	/**
424 	 * Set the volume.
425 	 */
426 	void setVolume(float volume) {
427 		this._volume = volume;
428 		
429 		alSourcef(this._alChunk.source, AL_GAIN, this._volume);
430 	}
431 	
432 	/**
433 	 * Set the volume.
434 	 */
435 	void setVolume(ubyte volume) {
436 		this.setVolume(volume / 255f);
437 	}
438 	
439 	/**
440 	 * Returns the current volume.
441 	 */
442 	ubyte getVolume() const pure nothrow {
443 		return cast(ubyte)(this._volume * ubyte.max);
444 	}
445 	
446 	/**
447 	 * Enable looping.
448 	 */
449 	void setLooping(bool enable) {
450 		this._looping = enable;
451 		
452 		alSourcei(this._alChunk.source, AL_LOOPING, this._looping);
453 	}
454 	
455 	/**
456 	 * Returns if the looping is enabled or not.
457 	 */
458 	bool getLooping() const pure nothrow {
459 		return this._looping;
460 	}
461 	
462 	/**
463 	 * Activate the playing.
464 	 * If enable is true, the looping is also activated.
465 	 */
466 	void play(bool enable) {
467 		this.setLooping(enable);
468 		this.play();
469 	}
470 	
471 	/**
472 	 * Activate the playing.
473 	 */
474 	void play() {
475 		alSourcePlay(this._alChunk.source);
476 		this._status = Status.Playing;
477 	}
478 	
479 	/**
480 	 * Stop current playing.
481 	 */
482 	void stop() {
483 		alSourceStop(this._alChunk.source);
484 		this._status = Status.Stopped;
485 	}
486 	
487 	/**
488 	 * Rewind playing.
489 	 */
490 	void rewind() {
491 		alSourceRewind(this._alChunk.source);
492 		this._status = Status.Playing;
493 	}
494 	
495 	/**
496 	 * Pause playing.
497 	 */
498 	void pause() {
499 		alSourcePause(this._alChunk.source);
500 		this._status = Status.Paused;
501 	}
502 	
503 	/**
504 	 * Set the position.
505 	 */
506 	void setPosition(ref const Vector3f vpos) {
507 		this._sourcePos = vpos;
508 		this._updatePosition();
509 	}
510 	
511 	/**
512 	 * Set the position.
513 	 */
514 	void setPosition(float x, float y, float z = 0) {
515 		this._sourcePos.set(x, y, z);
516 		this._updatePosition();
517 	}
518 	
519 	private void _updatePosition() const {
520 		const float[3] pos = this._sourcePos.asArray();
521 
522 		alSourcefv(this._alChunk.source, AL_POSITION, &pos[0]);
523 	}
524 	
525 	/**
526 	 * Returns the current position.
527 	 */
528 	ref const(Vector3f) getPosition() const pure nothrow {
529 		return this._sourcePos;
530 	}
531 	
532 	/**
533 	 * Set the velocity.
534 	 */
535 	void setVelocity(ref const Vector3f vvel) {
536 		this._sourceVel = vvel;
537 		this._updateVelocity();
538 	}
539 	
540 	/**
541 	 * Set the velocity.
542 	 */
543 	void setVelocity(float x, float y, float z = 0) {
544 		this._sourceVel.set(x, y, z);
545 		this._updateVelocity();
546 	}
547 	
548 	private void _updateVelocity() const {
549 		const float[3] vel = this._sourceVel.asArray();
550 
551 		alSourcefv(this._alChunk.source, AL_VELOCITY, &vel[0]);
552 	}
553 	
554 	/**
555 	 * Returns the current velocity.
556 	 */
557 	ref const(Vector3f) getVelocity() const pure nothrow {
558 		return this._sourceVel;
559 	}
560 }