Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Audio seems overly complicated :(

This thread is locked; no one can reply to it. rss feed Print
 1   2   3   4 
Audio seems overly complicated :(
Edgar Reynaldo
Major Reynaldo
May 2007
avatar

bamccaig
Member #7,536
July 2006
avatar

What I want to try to figure out is a dumb way to automatically wire this up as the user adds more samples to the system. Basically, create a simple API over the complicated API... I'm curious if something like that is easy to accomplish, or would be a brainfuck to get working. We could call it, allegro_audio_unfucked.h. :P

Elias
Member #358
May 2000

You don't have to worry about the user adding more samples. This is exactly why someone already added al_reserve_samples and al_play_sample - it takes care of everything else for you. The parameter to al_reserve_samples really doesn't matter all that much, just set it to 100 or something. If you play more than 100 sounds at the same time then you're doing something else wrong :P

--
"Either help out or stop whining" - Evert

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

bamccaig
Member #7,536
July 2006
avatar

Basically what I'm trying to build is a sort of virtual "piano". The user can spam a key that corresponds to a note and it should sound similar to if they did the same with a piano key. If I just reserve samples and play the sample each time the user presses the key (and stop the samples when the user releases the key) then the sounds lag and then stop all together. Presumably, because the lower-level system has already allocated all of those "instances" and I cannot play more. Another thing that I'm envisioning is if somebody attempted to develop a music mixing program with Allegro, where the user can select any number of sound files from their computer or possibly generate them using the program, and then freely play them back any number of times in any order, sequence, at any time. I'm trying to envision a library that can do that simply without the programmer having to think too hard. :P

roger levy
Member #2,513
July 2002

I gave up on Allegro 5 audio in favor of FMOD despite the license restrictions.

I sweated for days trying to figure it out, but could not get it to behave in a predictable way. The amount of setup, guesswork, and gotchas is unreasonable.

This is not a war between the past and the future, there is no reason why there can't be an easy layer on top that is as useful as the old one. The current "easy" API doesn't let you modify channel properties after they have started playing. I couldn't even figure out the best way to detect that anything has stopped playing in order to free up channels in my own implementation, which is in my opinion suboptimal that we should have to do that in the first place. Unless a lot has changed in the past few months, to me, the API is far from usable.

With FMOD on the other hand I was up and running within an hour and am not wanting for functionality. What's Allegro's excuse?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

@bambams
It would be easier to use a sample instance. Then you can adjust it's looping and playing much easier.

There should be one sample for each note, as well as one sample instance. When the key is down, set the sample instance playing to true and false when up. Keep the sound on a loop so it doesn't stop. Then each sample instance will mix with the others currently playing. It should come out like a piano.

You will probably run into hardware keyboard limitations though.

EDIT
@roger levy

The current "easy" API doesn't let you modify channel properties after they have started playing.

That's not true. al_play_sample gives you back an ALLEGRO_SAMPLE_ID, which you can al_lock_sample_id to get an ALLEGRO_SAMPLE_INSTANCE*, which you can then adjust to your hearts content.

roger levy said:

I couldn't even figure out the best way to detect that anything has stopped playing

al_get_sample_instance_playing does exactly what you want.

Elias
Member #358
May 2000

bamccaig said:

I'm trying to envision a library that can do that simply without the programmer having to think too hard.

Well, if al_reserve_samples(100) is not enough try al_reserve_samples(200). A piano only has 88 keys - and if you press the same key again it will interrupt the previous sound it made.

--
"Either help out or stop whining" - Evert

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

elias - I think you're mistaken about this but my tests don't seem to disagree.

I tried out using a default mixer with 1, 10, and 100 samples reserved. Nothing seems to change, despite playing multiple sounds repeatedly. Some distortion is noticeable if you play too many instances, but the volume per sample seemed to stay constant.

This is actually easier than I thought it was. I thought I would have to manage the per sample volume, but apparently not so.

EDIT
Nevermind, the number of samples passed to al_reserve_samples is a hard maximum. I was attaching sample instances directly to the default mixer, which works without limitation. Why does the default mixer have a maximum number of sounds anyway? If it's a regular mixer, then attaching sample instances to it shouldn't matter.

bamccaig
Member #7,536
July 2006
avatar

Elias said:

Well, if al_reserve_samples(100) is not enough try al_reserve_samples(200). A piano only has 88 keys - and if you press the same key again it will interrupt the previous sound it made.

I posted the program above I think. I'm reserving 49 (7 * 7) samples, and the program only supports 7 notes at the moment. When a key down event is seen I begin playing the sounds, and when a key up event is seen I stop the sample using the ID I was given. Can you see a reason why that program won't work? If I can avoid having to setup mixers and instances and such myself that would be great, but from the sounds of it, I need to dig into this lower-level if I want sounds to sound predictable/as instructed.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Izual
Member #2,756
September 2002
avatar

After reading this thread i just had to try the allegro 5 "complex" audio addon that everyone is talking about here.
I played with it for about an hour or two ( including recompiling allegro to enable audio and browsing the web for samples ) and found out that it really is not "complex".
Just a little bit more involved on the side of initial setup. But it is a very small price to pay for the amount of control you will gain over your samples.
I had no issue playing samples in any way i liked, modify them on the fly, or play whatever amount of them i wanted.
But when i was playing too many samples i had to use ALLEGRO_AUDIO_DEPTH_FLOAT32 for voice and mixers because with the ALLEGRO_AUDIO_DEPTH_INT16 the sound was getting a bit distorted.

I find the audio addon very well made, and really easy to use.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Neil Roy
Member #2,229
April 2002
avatar

Izual said:

I find the audio addon very well made, and really easy to use.

Yeah, next time try figuring it out without the help of a thread like this. I got no easy help like this years ago when I asked the same questions. And the official documentation has STILL not been updated.

Even Edgar got confused about it all a few years back if you search. So don't give me this bullshit.

---
“I love you too.” - last words of Wanda Roy

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

The passage you're quoting me from as saying "it's confusing" was referring to the documentation at the time. That was years ago. It's improved by leaps and bounds.

Like I said, if you have specific recommendations to make things easier, speak up!

roger levy
Member #2,513
July 2002

@Edgar

I'll admit I haven't used the API in at least a year. I threw up my hands the last time I tried to use it because it's so complex, but also because previously it was buggy and I needed to do a lot of workarounds.

The al_get_sample_instance_playing doesn't distinguish if the sample has stopped playing because it is finished or if you paused playback. Unless that has changed. Also IIRC it didn't work for one of the playable sound types and I had to code a workaround. This was years ago though. An event or callback would be better, then you are not constantly having to poll every sound.

I am glad you can now change properties of playing sounds. I will have to take another look. It'll be preferable to not need FMOD. But I still think Allegro 5 is too fussy and a simpler API similar to FMOD would be nice. Not everyone has the brain capacity and time to deal with a more powerful, more complicated API.

Last time I brought up that old shortcoming I was told that al_play_sample wasn't meant for advanced use and I needed to use the more lowlevel functions so I assumed it wasn't going to change any time soon.

(I had to implement my own dynamic channel system for The Lady, but if it's not needed anymore then al_get_sample_instance_playing is moot now.)

I just want to point out that defending things with your opinion, or "taking sides" when it comes to tools isn't helpful. Everybody is different, so a little more listening and a little less rhetoric would be good for everyone.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Hey roger,

I get what you're saying. At least you have specific concerns and not just general complaints. That is much easier to work with.

The al_get_sample_instance_playing doesn't distinguish if the sample has stopped playing because it is finished or if you paused playback. Unless that has changed. Also IIRC it didn't work for one of the playable sound types and I had to code a workaround. This was years ago though. An event or callback would be better, then you are not constantly having to poll every sound.

Yes. The audio API needs to be hooked up to the event system. However, that would mean you would need to register an ALLEGRO_EVENT_SOURCE attached to a sample instance. This might take some work, and I don't know myself how to do it straight away.

roger levy said:

I just want to point out that defending things with your opinion, or "taking sides" when it comes to tools isn't helpful. Everybody is different, so a little more listening and a little less rhetoric would be good for everyone.

Maybe so, but at the same time when people just repeat "it's too complicated" over and over without anything constructive to add, it gets tiresome. I'm listening for sure, but unless people make less vague suggestions as to what to do to improve the API, it's kind of pointless.

roger levy
Member #2,513
July 2002

Right, people need to recognize that this is a free open-source project, so actually proposing ideas for solutions is way more constructive than just complaining.

My suggestion is pretty straightforward - provide a layer as easy as FMOD! :)

bamccaig
Member #7,536
July 2006
avatar

"The audio API is too complicated," is constructive.

"Fuck this shit library," for example, would be unconstructive.

{"name":"f9e.gif","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/f\/b\/fb04f0c4bec3923359d4e1bb014af4e5.gif","w":305,"h":185,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/f\/b\/fb04f0c4bec3923359d4e1bb014af4e5"}f9e.gif

Neil Roy
Member #2,229
April 2002
avatar

I will give credit where credit is due. Edgar's example he shared in here was really excellent and helped a lot. So koodos for that.

---
“I love you too.” - last words of Wanda Roy

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Thanks Neil. I try to be helpful when I can.

bamccaig said:

"The audio API is too complicated," is constructive.

That statement is vague and useless without qualifying it.

Take a second and think about the API. Then make a concrete suggestion as to what could be improved. Saying "it could be simpler" is also vague and pointless.

If you want things to change, then you need to go deeper.

Is the documentation for a certain function lacking? What does it lack?

Do we need more functions? Or just different ones? Without specifics, there's really no point discussing it, as nothing will ever change.

What is it that is complicated? Sample instances? They seem straight forward enough for me. You have to create one from a sample, and then you have to attach it to a mixer. Okay, that's easy enough.

If all you want to do is play sound, then al_play_sample does the trick. If you want to change sound characteristics during playback, then you need a sample instance, and not a sample, because that's just static data. The instance is what tells the mixer how to play it. Keeping samples and instances separate is the way to go, as you can share the same data between several instances. This allows you to play multiple simultaneous occurrences of the same audio file, instead of loading several copies of the same data. Instances make sense. To me at least. ;)

@bamccaig
I'm trying out your program now.

EDIT
One thing I see is that you call al_play_sample with a gain of 3.0. This is bound to cause you problems. Normal gain is 1.0, which is full volume. You were also using the 'pp' media piano sounds, which means 'pianissimo', which is soft. I used the 'ff' which is forte, which is loud. Another problem is the delayed attack on the piano notes, so it doesn't coincide with the key press. You also can't press more than two or three keys at once without KEY_DOWN events getting lost. These are the main issues I see with your piano program.

I got your program to work though, with some slight modifications. Attached is Piano.zip (win32 binary and src and data)

To make a better piano program, you need better samples. Ones that start right away, and loop cleanly. The hardware keyboard limitation is gonna get you though.

bamccaig
Member #7,536
July 2006
avatar

If you want to change sound characteristics during playback, then you need a sample instance, and not a sample, because that's just static data. The instance is what tells the mixer how to play it. Keeping samples and instances separate is the way to go, as you can share the same data between several instances. This allows you to play multiple simultaneous occurrences of the same audio file, instead of loading several copies of the same data. Instances make sense. To me at least. ;)

This is a good explanation, but I think that the instances are really implementation details. Instead, al_play_sample() could accept a pointer to an ALLEGRO_SAMPLE_PARAMETERS or something like that which can override the play settings. Under the hood, Allegro may wish to track it in an instance (needs to copy it anyway so the user can free it), but the user will just get an ID back that it can use to further manipulate playback. The ID can be used to pause or possibly readjust parameters if that's possible while an instance is already playing (e.g., al_tune_playback(id, &settings) or al_stop_playback(id)).

All the user has is an ALLEGRO_SAMPLE for the data. If necessary, they can allocate a temporarily ALLEGRO_SAMPLE_PARAMETERS on the stack to tune the playback, and if they want to be able to control playback after it has started then they will want to store a simple integer ID too.

Allegro should do whatever it needs to under the hood to make this work: play the sound as instructed, and do its best to make it sound as the caller would expect. If this means it needs to wire up mixers and voices and whatnot then it should do it on the fly, but as an implementation detail the user doesn't have to care about. If it's resource intensive and might be too slow to do while the game is running, then Allegro should try to allocate a complex enough hierarchy to do anything that could be asked of it when the addon is initialized (hopefully just a voice and a couple of mixers or something).

Even the user facing streams could probably be eliminated and just make samples themselves streamable. If a sample is streamed then the data would not be loaded fully when the file is opened, and it would transparently act like a stream instead when played. If file-based then Allegro will load up chunks of the file when needed. Or if the user is generating the stream data on the fly then it'll just respond to an event the same way, but be filling in a sample id instead something like al_stream_playback(id, buffer, start, count) (instead of the current API of getting a buffer and then setting it again, which doesn't make much sense).

Append:

I finally took a stab at rewriting my program to use the lower-level API.

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_acodec.h> 3#include <allegro5/allegro_audio.h> 4#include <allegro5/allegro_font.h> 5#include <allegro5/allegro_ttf.h> 6#include <stdio.h> 7#include <stdlib.h> 8 9const int AUDIO_FREQUENCY = 44100; 10const ALLEGRO_AUDIO_DEPTH AUDIO_DEPTH = ALLEGRO_AUDIO_DEPTH_FLOAT32; 11const ALLEGRO_CHANNEL_CONF CHANNEL_CONF = ALLEGRO_CHANNEL_CONF_2; 12const int NUM_INSTANCES = 10000; 13 14#define notes_length (sizeof(notes) / sizeof(ALLEGRO_SAMPLE *)) 15 16#define say_center(w, h, text) \ 17 (al_draw_text(musicals, white, w, h, ALLEGRO_ALIGN_CENTRE, text)) 18 19static const char abcdefg[] = "abcdefg"; 20 21int get_available_instance(int, ALLEGRO_SAMPLE_INSTANCE * [7][10000]); 22bool play_note(int, ALLEGRO_SAMPLE_INSTANCE * [7][10000]); 23 24int main(int argc, char * argv[]) 25{ 26 char tmp[8] = " "; 27 int i = 0, il = -1, j = 0, jl = -1; 28 int status = 0; 29 ALLEGRO_COLOR black, white; 30 ALLEGRO_DISPLAY * display = NULL; 31 ALLEGRO_EVENT_QUEUE * event_queue = NULL; 32 ALLEGRO_FONT * musicals = NULL; 33 ALLEGRO_MIXER * mixer = NULL; 34 ALLEGRO_SAMPLE * notes[7] = {0}; 35 ALLEGRO_SAMPLE_INSTANCE * instances[7][10000] = {0}; 36 ALLEGRO_TIMEOUT timeout; 37 ALLEGRO_VOICE * voice = NULL; 38 39 if (!al_init()) { 40 fprintf(stderr, "al_init failed\n"); 41 goto error; 42 } 43 44 if (!al_install_keyboard()) { 45 fprintf(stderr, "al_install_keyboard failed\n"); 46 goto error; 47 } 48 49 if (!al_install_audio()) { 50 fprintf(stderr, "al_install_audio failed\n"); 51 goto error; 52 } 53 54 if (!al_init_acodec_addon()) { 55 fprintf(stderr, "al_init_acodec_addon failed\n"); 56 goto error; 57 } 58 59 if (!al_init_font_addon()) { 60 fprintf(stderr, "al_init_font_addon failed\n"); 61 goto error; 62 } 63 64 if (!al_init_ttf_addon()) { 65 fprintf(stderr, "al_init_ttf_addon failed\n"); 66 goto error; 67 } 68 69 display = al_create_display(1440, 900); 70 71 if (display == NULL) { 72 fprintf(stderr, "al_create_display failed\n"); 73 goto error; 74 } 75 76 event_queue = al_create_event_queue(); 77 78 if (event_queue == NULL) { 79 fprintf(stderr, "al_create_event_queue failed\n"); 80 goto error; 81 } 82 83 voice = al_create_voice( 84 AUDIO_FREQUENCY, 85 AUDIO_DEPTH, 86 CHANNEL_CONF); 87 88 if (voice == NULL) { 89 fprintf(stderr, "al_create_voice failed\n"); 90 goto error; 91 } 92 93 mixer = al_create_mixer( 94 AUDIO_FREQUENCY, 95 AUDIO_DEPTH, 96 CHANNEL_CONF); 97 98 if (mixer == NULL) { 99 fprintf(stderr, "al_create_mixer failed\n"); 100 goto error; 101 } 102 103 if (!al_attach_mixer_to_voice(mixer, voice)) { 104 fprintf(stderr, "al_attach_mixer_to_voice failed\n"); 105 goto error; 106 } 107 108 for (i=0,il=notes_length; i<il; i++) { 109 char filename[22] = "media/Piano.pp.A3.ogg"; 110 111 filename[15] = 'A' + i; 112 113 if (filename[15] > 'B') { 114 filename[16] = '4'; 115 } 116 117 notes[i] = al_load_sample(filename); 118 119 if (notes[i] == NULL) { 120 fprintf(stderr, "al_load_sample %s failed\n", filename); 121 goto error; 122 } else { 123 for(j=0,jl=NUM_INSTANCES; j<jl; j++) { 124 instances[i][j] = al_create_sample_instance(notes[i]); 125 126 if (instances[i][j] == NULL) { 127 fprintf(stderr, 128 "al_create_sample_instance %s[%d] failed\n", 129 filename, j); 130 goto error; 131 } 132 133 if (!al_attach_sample_instance_to_mixer(instances[i][j], mixer)) { 134 fprintf(stderr, 135 "al_attach_sample_instance_to_mixer %s[%d] failed\n", 136 filename, j); 137 goto error; 138 } 139 } 140 } 141 } 142 143 musicals = al_load_ttf_font("musicals.ttf", 72, 0); 144 145 if (musicals == NULL) { 146 fprintf(stderr, "al_load_ttf_font failed\n"); 147 goto error; 148 } 149 150 al_register_event_source(event_queue, 151 al_get_display_event_source(display)); 152 153 al_register_event_source(event_queue, 154 al_get_keyboard_event_source()); 155 156 black = al_map_rgb(0, 0, 0); 157 white = al_map_rgb(255, 255, 255); 158 159 al_init_timeout(&timeout, 0.06); 160 161 while (1) { 162 int w = al_get_display_width(display); 163 int h = al_get_display_height(display); 164 ALLEGRO_EVENT ev; 165 166 if (al_wait_for_event_until(event_queue, &ev, &timeout)) { 167 switch (ev.type) { 168 case ALLEGRO_EVENT_DISPLAY_CLOSE: 169 goto clean; 170 break; 171 case ALLEGRO_EVENT_KEY_DOWN: 172 if (ev.keyboard.keycode >= ALLEGRO_KEY_A && 173 ev.keyboard.keycode <= ALLEGRO_KEY_G) { 174 i = ev.keyboard.keycode - ALLEGRO_KEY_A; 175 tmp[i] = abcdefg[i]; 176 177 play_note(i, instances); 178 } else { 179 switch (ev.keyboard.keycode) { 180 case ALLEGRO_KEY_ESCAPE: 181 case ALLEGRO_KEY_Q: 182 goto clean; 183 } 184 } 185 186 break; 187 case ALLEGRO_EVENT_KEY_UP: 188 if (ev.keyboard.keycode >= ALLEGRO_KEY_A && 189 ev.keyboard.keycode <= ALLEGRO_KEY_G) { 190 i = ev.keyboard.keycode - ALLEGRO_KEY_A; 191 tmp[i] = ' '; 192 } 193 break; 194 } 195 } 196 197 al_clear_to_color(black); 198 199 say_center(w / 2.0, h / 2.0, "abcdefg ABCDEFG"); 200 say_center(w / 2.0, h / 2.0 + 100, tmp); 201 202 al_flip_display(); 203 } 204 205clean: 206 for (i=0,il=notes_length; i<il; i++) { 207 if (notes[i]) { 208 al_destroy_sample(notes[i]); 209 210 for (j=0,jl=NUM_INSTANCES; j<jl; j++) { 211 if (instances[i][j]) { 212 al_destroy_sample_instance(instances[i][j]); 213 } 214 } 215 } 216 } 217 if (musicals) al_destroy_font(musicals); 218 if (mixer) al_destroy_mixer(mixer); 219 if (voice) al_destroy_voice(voice); 220 if (event_queue) al_destroy_event_queue(event_queue); 221 if (display) al_destroy_display(display); 222 223 return status; 224 225error: 226 status = 1; 227 goto clean; 228} 229 230int get_available_instance( 231 int i, 232 ALLEGRO_SAMPLE_INSTANCE * instances[7][10000]) { 233 int j, l; 234 235 for (j=0, l=NUM_INSTANCES; j<l; j++) { 236 if (!al_get_sample_instance_playing(instances[i][j])) { 237 return j; 238 } 239 } 240 241 return -1; 242} 243 244bool play_note( 245 int i, 246 ALLEGRO_SAMPLE_INSTANCE * instances[7][10000]) { 247 int j = get_available_instance(i, instances); 248 249 if (j == -1) { 250 return false; 251 } 252 253 return al_set_sample_instance_playing( 254 instances[i][j], 255 true); 256}

diff --git a/src/main.c b/src/main.c
index ec60a45..e349017 100644
--- a/src/main.c
+++ b/src/main.c
@@ -6,33 +6,35 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+const int AUDIO_FREQUENCY = 44100;
+const ALLEGRO_AUDIO_DEPTH AUDIO_DEPTH = ALLEGRO_AUDIO_DEPTH_FLOAT32;
+const ALLEGRO_CHANNEL_CONF CHANNEL_CONF = ALLEGRO_CHANNEL_CONF_2;
+const int NUM_INSTANCES = 10000;
+
 #define notes_length (sizeof(notes) / sizeof(ALLEGRO_SAMPLE *))
 
 #define say_center(w, h, text) \
         (al_draw_text(musicals, white, w, h, ALLEGRO_ALIGN_CENTRE, text))
 
-#define play_note(i) \
-        (al_play_sample( \
-                 notes[i], \
-                 3.0, 0.0, 1.0, \
-                 ALLEGRO_PLAYMODE_ONCE, \
-                 &sample_ids[i]))
-
 static const char abcdefg[] = "abcdefg";
 
+int get_available_instance(int, ALLEGRO_SAMPLE_INSTANCE * [7][10000]);
+bool play_note(int, ALLEGRO_SAMPLE_INSTANCE * [7][10000]);
+
 int main(int argc, char * argv[])
 {
     char tmp[8] = "       ";
-    int i = 0, l = -1;
-    int playing[7] = {0};
+    int i = 0, il = -1, j = 0, jl = -1;
     int status = 0;
     ALLEGRO_COLOR black, white;
     ALLEGRO_DISPLAY * display = NULL;
     ALLEGRO_EVENT_QUEUE * event_queue = NULL;
     ALLEGRO_FONT * musicals = NULL;
+    ALLEGRO_MIXER * mixer = NULL;
     ALLEGRO_SAMPLE * notes[7] = {0};
-    ALLEGRO_SAMPLE_ID sample_ids[7] = {0};
+    ALLEGRO_SAMPLE_INSTANCE * instances[7][10000] = {0};
     ALLEGRO_TIMEOUT timeout;
+    ALLEGRO_VOICE * voice = NULL;
 
     if (!al_init()) {
         fprintf(stderr, "al_init failed\n");
@@ -78,12 +80,32 @@ int main(int argc, char * argv[])
         goto error;
     }
 
-    if (!al_reserve_samples(7 * 7)) {
-        fprintf(stderr, "al_reserve_samples failed\n");
+    voice = al_create_voice(
+            AUDIO_FREQUENCY,
+            AUDIO_DEPTH,
+            CHANNEL_CONF);
+
+    if (voice == NULL) {
+        fprintf(stderr, "al_create_voice failed\n");
+        goto error;
+    }
+
+    mixer = al_create_mixer(
+            AUDIO_FREQUENCY,
+            AUDIO_DEPTH,
+            CHANNEL_CONF);
+
+    if (mixer == NULL) {
+        fprintf(stderr, "al_create_mixer failed\n");
+        goto error;
+    }
+
+    if (!al_attach_mixer_to_voice(mixer, voice)) {
+        fprintf(stderr, "al_attach_mixer_to_voice failed\n");
         goto error;
     }
 
-    for (i=0,l=notes_length; i<l; i++) {
+    for (i=0,il=notes_length; i<il; i++) {
         char filename[22] = "media/Piano.pp.A3.ogg";
 
         filename[15] = 'A' + i;
@@ -97,6 +119,24 @@ int main(int argc, char * argv[])
         if (notes[i] == NULL) {
             fprintf(stderr, "al_load_sample %s failed\n", filename);
             goto error;
+        } else {
+            for(j=0,jl=NUM_INSTANCES; j<jl; j++) {
+                instances[i][j] = al_create_sample_instance(notes[i]);
+
+                if (instances[i][j] == NULL) {
+                    fprintf(stderr,
+                            "al_create_sample_instance %s[%d] failed\n",
+                            filename, j);
+                    goto error;
+                }
+
+                if (!al_attach_sample_instance_to_mixer(instances[i][j], mixer)) {
+                    fprintf(stderr,
+                            "al_attach_sample_instance_to_mixer %s[%d] failed\n",
+                            filename, j);
+                    goto error;
+                }
+            }
         }
     }
 
@@ -134,9 +174,7 @@ int main(int argc, char * argv[])
                         i = ev.keyboard.keycode - ALLEGRO_KEY_A;
                         tmp[i] = abcdefg[i];
 
-                        if (playing[i] == 0) {
-                            playing[i] = play_note(i);
-                        }
+                        play_note(i, instances);
                     } else {
                         switch (ev.keyboard.keycode) {
                             case ALLEGRO_KEY_ESCAPE:
@@ -151,11 +189,6 @@ int main(int argc, char * argv[])
                             ev.keyboard.keycode <= ALLEGRO_KEY_G) {
                         i = ev.keyboard.keycode - ALLEGRO_KEY_A;
                         tmp[i] = ' ';
-
-                        if (playing[i] != 0) {
-                            al_stop_sample(&sample_ids[i]);
-                            playing[i] = 0;
-                        }
                     }
                     break;
             }
@@ -170,10 +203,20 @@ int main(int argc, char * argv[])
     }
 
 clean:
-    for (i=0,l=notes_length; i<l; i++) {
-        if (notes[i]) al_destroy_sample(notes[i]);
+    for (i=0,il=notes_length; i<il; i++) {
+        if (notes[i]) {
+            al_destroy_sample(notes[i]);
+
+            for (j=0,jl=NUM_INSTANCES; j<jl; j++) {
+                if (instances[i][j]) {
+                    al_destroy_sample_instance(instances[i][j]);
+                }
+            }
+        }
     }
     if (musicals) al_destroy_font(musicals);
+    if (mixer) al_destroy_mixer(mixer);
+    if (voice) al_destroy_voice(voice);
     if (event_queue) al_destroy_event_queue(event_queue);
     if (display) al_destroy_display(display);
 
@@ -183,3 +226,31 @@ error:
     status = 1;
     goto clean;
 }
+
+int get_available_instance(
+        int i,
+        ALLEGRO_SAMPLE_INSTANCE * instances[7][10000]) {
+    int j, l;
+
+    for (j=0, l=NUM_INSTANCES; j<l; j++) {
+        if (!al_get_sample_instance_playing(instances[i][j])) {
+            return j;
+        }
+    }
+
+    return -1;
+}
+
+bool play_note(
+        int i,
+        ALLEGRO_SAMPLE_INSTANCE * instances[7][10000]) {
+    int j = get_available_instance(i, instances);
+
+    if (j == -1) {
+        return false;
+    }
+
+    return al_set_sample_instance_playing(
+           instances[i][j],
+           true);
+}

What I found was increased complexity having to manage a nested layer of objects. In my case, I am loading 7 "notes", and to allow notes to be repeatedly played I try to keep many samples around for each note. I started with 7, but the program quickly ran out of air again. I presume because the sample files that I happened to find vary in length up to a minute or so. It seems evident that I need to trim the files down to a shorter slice, or else find better files. That said, I tried creating 10000 instances per note (see code above for that hack) and still resulted in gittering and unpredictable behavior. Not only did the sounds eventually stop working (for a while anyway) again, but it seemed the more instances I fired up by repeatedly pressing "A" the more "pops" I'd hear. Pops that are not part of the sample data. Am I doing something wrong now with the lower level?

I hope that I'm missing something obvious here. If this is truly the direction my program has to go then I think it would illustrate why the current API is bad.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Comments for your reply first :

bamccaig said:

This is a good explanation, but I think that the instances are really implementation details. Instead, al_play_sample() could accept a pointer to an ALLEGRO_SAMPLE_PARAMETERS or something like that which can override the play settings. Under the hood, Allegro may wish to track it in an instance (needs to copy it anyway so the user can free it), but the user will just get an ID back that it can use to further manipulate playback. The ID can be used to pause or possibly readjust parameters if that's possible while an instance is already playing (e.g., al_tune_playback(id, &settings) or al_stop_playback(id)).

This is exactly what Allegro does already. ;)

bamccaig said:

All the user has is an ALLEGRO_SAMPLE for the data. If necessary, they can allocate a temporarily ALLEGRO_SAMPLE_PARAMETERS on the stack to tune the playback, and if they want to be able to control playback after it has started then they will want to store a simple integer ID too.

Allegro should do whatever it needs to under the hood to make this work: play the sound as instructed, and do its best to make it sound as the caller would expect. If this means it needs to wire up mixers and voices and whatnot then it should do it on the fly, but as an implementation detail the user doesn't have to care about. If it's resource intensive and might be too slow to do while the game is running, then Allegro should try to allocate a complex enough hierarchy to do anything that could be asked of it when the addon is initialized (hopefully just a voice and a couple of mixers or something).

Again, Allegro already does this. al_reserve_samples sets up a default voice and mixer, and sample instances tell allegro how to play a sample. When you call al_play_sample, it uses one of the reserved sample instances to play your sound.

bamccaig said:

Even the user facing streams could probably be eliminated and just make samples themselves streamable. If a sample is streamed then the data would not be loaded fully when the file is opened, and it would transparently act like a stream instead when played. If file-based then Allegro will load up chunks of the file when needed. Or if the user is generating the stream data on the fly then it'll just respond to an event the same way, but be filling in a sample id instead something like al_stream_playback(id, buffer, start, count) (instead of the current API of getting a buffer and then setting it again, which doesn't make much sense).

I don't know much about ALLEGRO_AUDIO_STREAMs, but it seems silly to stream small samples. Streams only make sense to me for large files and custom audio.

bamccaig said:

I finally took a stab at rewriting my program to use the lower-level API.

After replacing the note files and changing the voice to use ALLEGRO_AUDIO_DEPTH_INT16 (it's the only supported audio depth for a DirectSound voice (I'm on Windoze) ), everything works as expected.

What OS are you on?

Also, you don't need to create your own voice and mixer. Just call al_reserve_samples(1) to create the default voice and mixer. Then attach all your sample instances to the default mixer using al_get_default_mixer(). It will still work. I didn't get any distortion or pops or anything when playing sounds with your code. So the problem may be with your audio driver. You can change the driver using allegro5.cfg. I've attached a copy from the allegro source distribution. Look in the file under the [audio] section, and try changing the audio driver. That may fix things, it may not.

bamccaig
Member #7,536
July 2006
avatar

I don't know much about ALLEGRO_AUDIO_STREAMs, but it seems silly to stream small samples. Streams only make sense to me for large files and custom audio.

I meant for large files. :)

Quote:

Again, Allegro already does this. al_reserve_samples sets up a default voice and mixer, and sample instances tell allegro how to play a sample. When you call al_play_sample, it uses one of the reserved sample instances to play your sound.

Then why doesn't my original program work? :P Did you ever get a chance to try that?

Quote:

After replacing the note files and changing the voice to use ALLEGRO_AUDIO_DEPTH_INT16 (it's the only supported audio depth for a DirectSound voice (I'm on Windoze) ), everything works as expected.

What files did you use to test? I wonder if those would have an affect? Can you share the Windows build and files for me to try in Windows? Source too please so I can compare.

Quote:

Also, you don't need to create your own voice and mixer. Just call al_reserve_samples(1) to create the default voice and mixer. Then attach all your sample instances to the default mixer using al_get_default_mixer(). It will still work.

I just figured the best way to rule out other "magical" explanations was to do it all myself. This is pretty much exploration at this point to figure out what works so that I can learn from it and understand it.

Quote:

What OS are you on?

Ubuntu 16.04 (Xenial Xerces).

Quote:

I didn't get any distortion or pops or anything when playing sounds with your code. So the problem may be with your audio driver. You can change the driver using allegro5.cfg. I've attached a copy from the allegro source distribution. Look in the file under the [audio] section, and try changing the audio driver. That may fix things, it may not.

Interesting. I'll give the different driver options a try tonight.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quote:

bamccaig said:

Again, Allegro already does this. al_reserve_samples sets up a default voice and mixer, and sample instances tell allegro how to play a sample. When you call al_play_sample, it uses one of the reserved sample instances to play your sound.

Then why doesn't my original program work? :P Did you ever get a chance to try that?

It did work, after fixing the gain and the sound files, for me on Windows at least. Piano.zip has the source, media, and binary I made that shows this.

bamccaig said:

After replacing the note files and changing the voice to use ALLEGRO_AUDIO_DEPTH_INT16 (it's the only supported audio depth for a DirectSound voice (I'm on Windoze) ), everything works as expected.

What files did you use to test? I wonder if those would have an affect? Can you share the Windows build and files for me to try in Windows? Source too please so I can compare.

Attached is your second program, with the modifications I mentioned. It works for me too.

BCPiano.zip

So it seems you can still attach sample instances to the mixer created by al_reserve_samples, regardless of the number of instances you reserve. I used al_reserve_samples(1) and attached multiple sample instances and they all played without distortion or dropping out.

 1   2   3   4 


Go to: