|
[A5] Playing Samples on the Fly? |
jmasterx
Member #11,410
October 2009
|
In my game, I have lots of short sounds that will be event triggered. However, I don't want to load them all into memory, instead, I just want to load, play, and destroy them after they are done playing. Is there a way to do this? Mainly, is there an event I can receive when a sample has finished playing? I can do it via polling but that feels like a big hack. Thanks Agui GUI API -> https://github.com/jmasterx/Agui |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Not currently, no. Which part of Allegro would fire off the audio events? The mixer, or the voice? I suppose it would have to be the mixer. It should have a list of SAMPLE_IDS that it is playing, and it should be able to detect when that sample is done playing. But when is the event fired? During the buffer update? That could lead to destroying a sample before it is finished mixing. Remember the sample that quit and post it to a queue of events to fire off before the next mixer update? I'm sure allegro could be hacked to do this, but I haven't looked at the actual code. There's probably a mixer callback controlled by a timer in a separate thread. I personally would like to see this happen too. No promises, but I'll try to take a look at the code to at least see how feasible this is, and what would need to be done. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
jmasterx
Member #11,410
October 2009
|
Yeah, I guess it's a bit trickier the way Allegro is designed. When I coded on ios a few months ago, every AVAudioPlayer object can attach a listener (AVAudioDelegate) and you get a callback when the player finishes playing. But that doesn't involve a mixer and causes things like global gain to be trickier to implement. it would be nice to have something like: al_register_sample_instance_callback((void* callback(ALLEGRO_SAMPLE_INSTANCE* spl, ALLEGRO_MIXER* mixer, bool finished)); So if the sample gets destroyed, it could dispatch the callback with finished = false. But this probably would create all kinds of problems I'm not considering. Agui GUI API -> https://github.com/jmasterx/Agui |
Edgar Reynaldo
Major Reynaldo
May 2007
|
I think the magic happens in MAKE_MIXER, specifically in fix_looped_position. 280#define MAKE_MIXER(NAME, NEXT_SAMPLE_VALUE, TYPE) \
281static void NAME(void *source, void **vbuf, unsigned int *samples, \
282 ALLEGRO_AUDIO_DEPTH buffer_depth, size_t dest_maxc) \
283{ \
284 ALLEGRO_SAMPLE_INSTANCE *spl = (ALLEGRO_SAMPLE_INSTANCE *)source; \
285 TYPE *buf = *vbuf; \
286 size_t maxc = al_get_channel_count(spl->spl_data.chan_conf); \
287 size_t samples_l = *samples; \
288 size_t c; \
289 int delta, delta_error; \
290 SAMP_BUF samp_buf; \
291 \
292 BRESENHAM; \
293 \
294 if (!spl->is_playing) \
295 return; \
296 \
297 while (samples_l > 0) { \
298 const TYPE *s; \
299 int old_step = spl->step; \
300 \
301 if (!fix_looped_position(spl)) \
302 return; \
303 if (old_step != spl->step) { \
304 BRESENHAM; \
305 } \
306 \
307 /* It might be worth preparing multiple sample values at once. */ \
308 s = (TYPE *) NEXT_SAMPLE_VALUE(&samp_buf, spl, maxc); \
309 \
310 for (c = 0; c < dest_maxc; c++) { \
311 ALLEGRO_STATIC_ASSERT(kcm_mixer, ALLEGRO_MAX_CHANNELS == 8); \
312 switch (maxc) { \
313 /* Each case falls through. */ \
314 case 8: *buf += s[7] * spl->matrix[c*maxc + 7]; \
315 case 7: *buf += s[6] * spl->matrix[c*maxc + 6]; \
316 case 6: *buf += s[5] * spl->matrix[c*maxc + 5]; \
317 case 5: *buf += s[4] * spl->matrix[c*maxc + 4]; \
318 case 4: *buf += s[3] * spl->matrix[c*maxc + 3]; \
319 case 3: *buf += s[2] * spl->matrix[c*maxc + 2]; \
320 case 2: *buf += s[1] * spl->matrix[c*maxc + 1]; \
321 case 1: *buf += s[0] * spl->matrix[c*maxc + 0]; \
322 default: break; \
323 } \
324 buf++; \
325 } \
326 \
327 spl->pos += delta; \
328 spl->pos_bresenham_error += delta_error; \
329 if (spl->pos_bresenham_error >= spl->step_denom) { \
330 spl->pos++; \
331 spl->pos_bresenham_error -= spl->step_denom; \
332 } \
333 samples_l--; \
334 } \
335 fix_looped_position(spl); \
336 (void)buffer_depth; \
337}
338
339MAKE_MIXER(read_to_mixer_point_float_32, point_spl32, float)
340MAKE_MIXER(read_to_mixer_linear_float_32, linear_spl32, float)
341MAKE_MIXER(read_to_mixer_cubic_float_32, cubic_spl32, float)
342MAKE_MIXER(read_to_mixer_point_int16_t_16, point_spl16, int16_t)
343MAKE_MIXER(read_to_mixer_linear_int16_t_16, linear_spl16, int16_t)
344
345#undef MAKE_MIXER
165/* fix_looped_position:
166 * When a stream loops, this will fix up the position and anything else to
167 * allow it to safely continue playing as expected. Returns false if it
168 * should stop being mixed.
169 */
170static bool fix_looped_position(ALLEGRO_SAMPLE_INSTANCE *spl)
171{
172 bool is_empty;
173 ALLEGRO_AUDIO_STREAM *stream;
174
175 /* Looping! Should be mostly self-explanatory */
176 switch (spl->loop) {
177 case ALLEGRO_PLAYMODE_LOOP:
178 if (spl->loop_end - spl->loop_start != 0) {
179 if (spl->step > 0) {
180 while (spl->pos >= spl->loop_end) {
181 spl->pos -= (spl->loop_end - spl->loop_start);
182 }
183 }
184 else if (spl->step < 0) {
185 while (spl->pos < spl->loop_start) {
186 spl->pos += (spl->loop_end - spl->loop_start);
187 }
188 }
189 }
190 return true;
191
192 case ALLEGRO_PLAYMODE_BIDIR:
193 /* When doing bi-directional looping, you need to do a follow-up
194 * check for the opposite direction if a loop occurred, otherwise
195 * you could end up misplaced on small, high-step loops.
196 */
197 if (spl->loop_end - spl->loop_start != 0) {
198 if (spl->step >= 0) {
199 check_forward:
200 if (spl->pos >= spl->loop_end) {
201 spl->step = -spl->step;
202 spl->pos = spl->loop_end - (spl->pos - spl->loop_end) - 1;
203 goto check_backward;
204 }
205 }
206 else {
207 check_backward:
208 if (spl->pos < spl->loop_start || spl->pos >= spl->loop_end) {
209 spl->step = -spl->step;
210 spl->pos = spl->loop_start + (spl->loop_start - spl->pos);
211 goto check_forward;
212 }
213 }
214 }
215 return true;
216
217 case ALLEGRO_PLAYMODE_ONCE:
218 if (spl->pos < spl->spl_data.len) {
219 return true;
220 }
221 spl->pos = 0;
222 spl->is_playing = false;
223 return false;
224
225 case _ALLEGRO_PLAYMODE_STREAM_ONCE:
226 case _ALLEGRO_PLAYMODE_STREAM_ONEDIR:
227 if (spl->pos < spl->spl_data.len) {
228 return true;
229 }
230 stream = (ALLEGRO_AUDIO_STREAM *)spl;
231 is_empty = !_al_kcm_refill_stream(stream);
232 if (is_empty && stream->is_draining) {
233 stream->spl.is_playing = false;
234 }
235
236 _al_kcm_emit_stream_events(stream);
237
238 return !(is_empty);
239 }
240
241 ASSERT(false);
242 return false;
243}
In the case of ALLEGRO_PLAYMODE_ONCE fix_looped_position returns false. This could be used as a signal by MAKE_MIXER to fire a sample over event. You would also have to account for ALLEGRO_PLAYMODE_LOOP and BIDIR too, but fix_looped_position returns true for those, since they haven't stopped playing yet. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Thomas Fjellstrom
Member #476
June 2000
|
I've always thought that the audio add-on needed to fire off events. But that never happened. -- |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Well, I think it makes sense to emit events when a sample / stream is over / loops. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
SiegeLord
Member #7,827
October 2006
|
My problem with using events for this is that it satisfies this use case only, and no other use cases. E.g. what if you wanted to play a second sample as soon as the first ended? You can't use an event for that, as there will a gap between when the sample ends and when the event is processed. So from my point of view, this is where you pull out the good old ALLEGRO_AUDIO_STREAM and stream the samples yourself. EDIT: The backends do have a finite buffer that provides you some latency during which this can be done, but there's no API to configure its size... that's a failing of the current system, I think. In principle, if you didn't mind the latency and had a dedicated thread for audio processing, events might work ok. "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
Chris Katko
Member #1,881
January 2002
|
SiegeLord said: You can't use an event for that, as there will a gap between when the sample ends and when the event is processed. Allow firing the event some X time before it ends, allowing the handler time to be ready to cue up the next sample. Disclaimer: I've not used Allegro 5 much. -----sig: |
SiegeLord
Member #7,827
October 2006
|
Chris Katko said: Allow firing the event some X time before it ends, allowing the handler time to be ready to cue up the next sample. The delay is still non-deterministic (the audio runs on a separate thread, there's no synchronization between it and the main thread). There is also no mechanism to do 'conditional playback' of a sample. One final alternative to all this is to add a yet another audio source, that is based on callbacks. It'd be very similar to ALLEGRO_AUDIO_STREAM but would have no internal buffering or anything else of the sort. "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
Elias
Member #358
May 2000
|
SiegeLord said: So from my point of view, this is where you pull out the good old ALLEGRO_AUDIO_STREAM and stream the samples yourself. +1 -- |
jmasterx
Member #11,410
October 2009
|
For my use case, having a delay from the time the sample ends to the time the event is sent is perfectly okay. My use case was strictly for memory management purposes; the case when you have 100's of samples and one of them can be played at any given time, and you want to avoid loading all 100 samples into memory. I could easily stop a prior sample before starting another, but that would not sound right. So I'll probably instead poll and check if my samples are done and destroy them as they finish playing. Agui GUI API -> https://github.com/jmasterx/Agui |
Edgar Reynaldo
Major Reynaldo
May 2007
|
What kind of latency are we talking about here? How big is the voice buffer, and how often is it refilled? My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
SiegeLord
Member #7,827
October 2006
|
Edgar Reynaldo said: What kind of latency are we talking about here? How big is the voice buffer, and how often is it refilled? This is backend dependent, which is the crux of the issue here: it's unpredictable. I haven't looked if this is just an API oversight, or some backends don't have a meaningful number you can change/query. "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
Gideon Weems
Member #3,925
October 2003
|
|