1 module prova.audio.audio;
2 
3 import derelict.openal.al,
4        prova.audio,
5        prova.core,
6        prova.math;
7 
8 private class AudioBuffer
9 {
10   uint id;
11   int channels;
12 
13   ~this()
14   {
15     alDeleteBuffers(1, &id);
16   }
17 }
18 
19 /// 
20 class Audio
21 {
22   /// What distance in units equals one meter (defaults to 1)
23   static float scale = 1;
24   package(prova) static ALCdevice* device;
25   package(prova) static ALCcontext* context;
26   private static AudioBuffer[string] bufferCache;
27 
28   package(prova) Entity entity;
29   private uint sourceId;
30   private AudioBuffer buffer;
31   private bool _looping = false;
32   private float _volume = 1;
33   private float _pitch = 1;
34 
35   /// Limited to Ogg files for now
36   this(string path)
37   {
38     if(!(path in bufferCache)) 
39       cacheFile(path);
40 
41     buffer = bufferCache[path];
42 
43     alGenSources(1, &sourceId);
44     alSourcei(sourceId, AL_BUFFER, buffer.id);
45   }
46 
47   ///
48   @property uint channels()
49   {
50     return buffer.channels;
51   }
52 
53   ///
54   @property float volume()
55   {
56     return _volume;
57   }
58 
59   ///
60   @property void volume(float value)
61   {
62     _volume = value;
63     alSourcef(sourceId, AL_GAIN, value);
64   }
65 
66   ///
67   @property float pitch()
68   {
69     return _pitch;
70   }
71 
72   ///
73   @property void pitch(float value)
74   {
75     _pitch = value;
76     alSourcef(sourceId, AL_PITCH, value);
77   }
78 
79   ///
80   @property bool isPlaying()
81   {
82     ALenum status;
83     alGetSourcei(sourceId, AL_SOURCE_STATE, &status);
84 
85     return status == AL_PLAYING;
86   }
87 
88   ///
89   @property bool looping()
90   {
91     return _looping;
92   }
93 
94   ///
95   void play(bool loop = false)
96   {
97     if(isPlaying())
98       stop();
99 
100     _looping = loop;
101     alSourcei(sourceId, AL_LOOPING, loop);
102 
103     alSourceRewind(sourceId);
104     alSourcePlay(sourceId);
105   }
106 
107   ///
108   void stop()
109   {
110     alSourceStop(sourceId);
111   }
112 
113   ///
114   void pause()
115   {
116     alSourcePause(sourceId);
117   }
118 
119   ///
120   void resume()
121   {
122     alSourcePlay(sourceId);
123   }
124 
125   package(prova) void update()
126   {
127     if(!entity)
128       return;
129 
130     Vector3 position = entity.position / scale;
131     Vector3 velocity = entity.velocity / scale;
132 
133     alSource3f(sourceId, AL_POSITION, position.x, position.y, position.z);
134     alSource3f(sourceId, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
135   }
136 
137   ~this()
138   {
139     if(isPlaying)
140       stop();
141 
142     alDeleteSources(1, &sourceId);
143   }
144 
145   /// Limited to Ogg files for now
146   static void cacheFile(string path)
147   {
148     AudioFile file = new OggFile(path);
149 
150     genBuffer(path, file.channels, file.data, file.frequency);
151   }
152 
153   private static void genBuffer(string path, int channels, byte[] data, int frequency)
154   {
155     AudioBuffer buffer = new AudioBuffer();
156     buffer.channels = channels;
157 
158     ALenum format = channels ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
159 
160     alGenBuffers(1, &buffer.id);
161     alBufferData(buffer.id, format, data.ptr, cast(int) data.length, frequency);
162 
163     bufferCache[path] = buffer;
164   }
165 
166   package(prova) static void cleanUp()
167   {
168     bufferCache.clear();
169   }
170 }