|
Audio waveform with unexpected frequency behavior |
Bluebird
Member #15,421
December 2013
|
Hello. I've been playing around with audio recently, specifically trying to code a simple synthesizer. It works fine when only unchanging (static) frequencies are used (see ex_synth), but as soon as I try to interpolate a frequency when generating the waveform, things go ... wonky. Here's the clearest explanation of the problem that I know how to give: the program generates a waveform. The waveform should ramp smoothly from a lower frequency to a higher frequency, without detour. But instead, the waveform ramps too high (for reasons I can't even begin to guess at) before finally coming down to the target. The code is a complete program that demonstrates the problem. 1
2// This program generates a 5-second audio waveform that is supposed to ramp up
3// smoothly from 220 hertz to 620 hertz in 4 seconds, with 1 remaining second at
4// 620 hertz.
5//
6// The expected result is that the audio sound ramps up smoothly from one
7// frequency to another. Directly. Don't pass GO, don't collect $200.
8//
9// The actual result is that the frequency ramps up higher than it should, then
10// comes back down to the target frequency. How odd!
11//
12// The described problem is denied by the printouts generated when the program
13// is run, which indicate that the frequency *should be* ramping as expected
14// without frequency artifacts. But of course, the actual generated sound
15// contradicts this. Take note of the 'Freq' column; it proves that the
16// interpolation function works as expected. I'm pretty sure the problem isn't
17// there -- the interpolation function is used in many other places in the real
18// codebase without any issues. The algorithm was extracted off a math site.
19//
20// Note: in other tests the waveform was written to a file and inspected in an
21// audio editor. The saved waveform exhibits the same problem.
22
23#include <cstdint>
24#include <cstddef>
25#include <cstdlib>
26#include <cassert>
27#include <cstdio>
28#include <cmath>
29
30#include <allegro5/allegro.h>
31#include <allegro5/allegro_audio.h>
32
33// Interpolates from one frequency to another within a duration.
34float interpolate(float time, float base, float change, float duration)
35{
36 assert(duration > 0.0);
37 auto pi = (float)ALLEGRO_PI;
38
39 if(time <= 0.0) return base;
40 if(time >= duration) return base + change;
41
42 // This is a simple sinusoidal in/out interpolation. This math has been
43 // well-tested in the real codebase for other purposes, there are no known
44 // problems with it.
45 return -change / 2.0f * (std::cos(pi * time / duration) - 1.0f) + base;
46}
47
48// Generates the waveform.
49void waveform(
50 float* buffer, int32_t samples, float time, uint64_t count, float vfreq)
51{
52 auto pi = (float)ALLEGRO_PI;
53 auto pi2 = pi * 2.0f;
54 auto dt = 1.0f / vfreq;
55
56 for(int32_t i = 0; i < samples; ++i) {
57 float pt = (float)i;
58 float ti = time + pt * dt;
59
60 // Interpolate smoothly from 220 to 620 in 4 seconds.
61 float wave = interpolate(ti, 220.0f, 400.0f, 4.0f) * pi2;
62
63 // Print variables every 1/32 second.
64 if((count + i) % (44100 / 32) == 0) {
65 printf("Time %f : Hertz %f : Freq %f : Arg %lf\n",
66 ti, wave, wave / pi2, wave * ti);
67 }
68
69 float va = std::sin(wave * ti);
70 buffer[i] = va;
71 }
72}
73
74int32_t main(int32_t, const char**)
75{
76 // Declare variables in advance because using 'goto' for cleanup.
77 bool success = false;
78
79 // Keep track of what items need cleaning up.
80 bool have_alleg = false;
81 bool have_audio = false;
82 bool have_voice = false;
83 bool have_mixer = false;
84 bool have_stream = false;
85
86 bool mixer_attached = false;
87 bool stream_attached = false;
88
89 // Set when time to exit.
90 bool done = false;
91
92 ALLEGRO_VOICE* voice = nullptr;
93 ALLEGRO_MIXER* mixer = nullptr;
94 ALLEGRO_AUDIO_STREAM* stream = nullptr;
95
96 // Voice/mixer/stream frequency. Same variable, 2 representations.
97 auto vfreq = 44100;
98 auto ffreq = 44100.0f;
99
100 auto idepth = ALLEGRO_AUDIO_DEPTH_INT16;
101 auto fdepth = ALLEGRO_AUDIO_DEPTH_FLOAT32;
102 auto vchan = ALLEGRO_CHANNEL_CONF_2;
103 auto schan = ALLEGRO_CHANNEL_CONF_1;
104 auto fragments = 8; // Number of sample buffers.
105
106 // Samples per buffer. Same variable, 2 representations.
107 auto samples = 1024;
108 auto fsamps = 1024.0f;
109
110 float time = 0.0f;
111 float dt = 1.0f / ffreq;
112
113 // Will store current sample value (1024 per second).
114 uint64_t count = 0;
115
116 // Initializations.
117 if(!al_install_system(ALLEGRO_VERSION_INT, atexit)) {
118 printf("Could not init Allegro.\n");
119 goto cleanup;
120 } else have_alleg = true;
121
122 if(!al_install_audio()) {
123 printf("Could not install audio.\n");
124 goto cleanup;
125 } else have_audio = true;
126
127 voice = al_create_voice(vfreq, idepth, vchan);
128 if(!voice) {
129 printf("Could not create hardware voice.\n");
130 goto cleanup;
131 } else have_voice = true;
132
133 mixer = al_create_mixer(vfreq, fdepth, vchan);
134 if(!mixer) {
135 printf("Could not create mixer.\n");
136 goto cleanup;
137 } else have_mixer = true;
138
139 mixer_attached = al_attach_mixer_to_voice(mixer, voice);
140 if(!mixer_attached) {
141 printf("Could not attach mixer to voice.\n");
142 goto cleanup;
143 }
144
145 stream = al_create_audio_stream(fragments, samples, vfreq, fdepth, schan);
146 if(!stream) {
147 printf("Could not create audio stream.\n");
148 goto cleanup;
149 } else have_stream = true;
150
151 stream_attached = al_attach_audio_stream_to_mixer(stream, mixer);
152 if(!stream_attached) {
153 printf("Could not attach audio stream to mixer.\n");
154 goto cleanup;
155 }
156
157 // Main loop.
158 while(!done) {
159 void* buffer = al_get_audio_stream_fragment(stream);
160 if(!buffer) {
161 al_rest(0.001f); // Don't be a timeslice hog.
162 continue;
163 }
164
165 // Generate waveform.
166 waveform((float*)buffer, samples, time, count, ffreq);
167
168 // Advance time and sample count.
169 time += (dt * fsamps);
170 count += samples;
171
172 al_set_audio_stream_fragment(stream, buffer);
173 if(time >= 5.0f) done = true; // Quit after 5 seconds.
174 }
175
176 al_drain_audio_stream(stream);
177
178 success = true;
179 cleanup:
180
181 // Cleanup.
182 if(have_stream) {
183 al_detach_audio_stream(stream);
184 al_destroy_audio_stream(stream);
185 }
186
187 if(have_mixer) {
188 al_detach_mixer(mixer);
189 al_destroy_mixer(mixer);
190 }
191
192 if(have_voice) {
193 al_detach_voice(voice);
194 al_destroy_voice(voice);
195 }
196
197 if(have_audio) al_uninstall_audio();
198 if(have_alleg) al_uninstall_system();
199
200 if(success) {
201 printf("Everything worked.\n");
202 return EXIT_SUCCESS;
203 }
204
205 printf("Something went wrong.\n");
206 return EXIT_FAILURE;
207}
I've been banging my head on this for three days now ... What could possibly be the problem? |
GullRaDriel
Member #3,861
September 2003
|
I think it has to be something lying inside the types conversions. Have you enabled all the possible warnings to search and see ? I would try to launch it in valgrind, to check for overflows. Edit: I would also not use 'auto' and clearly choose a defined type instead. "Code is like shit - it only smells if it is not yours" |
MikiZX
Member #17,092
June 2019
|
Possibly you could try testing the code by replacing the interpolate function with a linear one. This should show if the input values you have at that point in your code are correct. Also, in your interpolate function try having a single point of return (i.e. first two return command could only set the temporary 'time' value for the final return to calculate the output). |
Edgar Reynaldo
Major Reynaldo
May 2007
|
You're interpolating from cos(0) to cos(PI). This means your wave is scaled by from 1 to 0 to -1 as it progresses. Try linear interpolation first, then mess with it. I'm going to try your code and see if I can get it to work. Replacing your interpolate function with a strict linear interpolation made the code work. // Interpolate smoothly from 220 to 620 in 4 seconds. float wave = (220.0f + 100*ti) * pi2; Ooh, try this : float wave = (440.0f + 220.0f*(sin(pi2*(count + i)/(float)samples))/4.0f) * pi2; I call it the SoundBlaster 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 |
Bluebird
Member #15,421
December 2013
|
Ouch, my ears! I've updated the program to address the remarks so far. Regarding the use of auto and type conversions; trust me, if it was something simple like that I would have found it already. But anyway, all autos are replaced with concrete types. Also note, this program compiles without warnings using the usual flags: `-Wall -Weffc++ -Wextra -Wpedantic -Wconversion`. I've added two new functions, linear interpolation and a function for discrete steps. The discrete step function produces the expected results as long as the step count is relatively low. Try making the step count 500 or higher; you'll start to notice the frequencies going too high. I've added a command-line switch so you can test the different functions easily. Edgar Reynaldo, I added your linear interpolate function, it's different from mine in that it doesn't have a specific target value, it just keeps increasing. Notice the problem shows up as soon as a target value is added to the code (command-line opt "edgar2"). 1
2// This program generates a 5-second audio waveform that is supposed to ramp up
3// smoothly from 220 hertz to 620 hertz in 4 seconds, with 1 remaining second at
4// 620 hertz.
5//
6// The expected result is that the audio sound ramps up smoothly from one
7// frequency to another. Directly. Don't pass GO, don't collect $200.
8//
9// The actual result is that the frequency ramps up higher than it should, then
10// comes back down to the target frequency. How odd!
11//
12// The described problem is denied by the printouts generated when the program
13// is run, which indicate that the frequency *should be* ramping as expected
14// without frequency artifacts. But of course, the actual generated sound
15// contradicts this. Take note of the 'Freq' column; it proves that the
16// interpolation function works as expected. I'm pretty sure the problem isn't
17// there -- the interpolation function is used in many other places in the real
18// codebase without any issues. The algorithm was extracted off a math site.
19//
20// Note: in other tests the waveform was written to a file and inspected in an
21// audio editor. The saved waveform exhibits the same problem.
22
23#include <cstdint>
24#include <cstddef>
25#include <cstdlib>
26#include <cassert>
27#include <cstdio>
28#include <cstring>
29#include <cmath>
30
31#include <string>
32#include <exception>
33#include <stdexcept>
34
35#include <allegro5/allegro.h>
36#include <allegro5/allegro_audio.h>
37
38float g_step_count = 20.0f;
39
40// Interpolates from one frequency to another within a duration.
41float interpolate_sinusoidal_inout(
42 float time, float base, float change, float duration)
43{
44 assert(duration > 0.0);
45 float pi = (float)ALLEGRO_PI;
46
47 if(time <= 0.0) time = 0.0f;
48 if(time >= duration) time = duration;
49
50 // This is a simple sinusoidal in/out interpolation. This math has been
51 // well-tested in the real codebase for other purposes, there are no known
52 // problems with it.
53 return -change / 2.0f * (std::cos(pi * time / duration) - 1.0f) + base;
54}
55
56float interpolate_linear_tween(
57 float time, float base, float change, float duration)
58{
59 assert(duration > 0.0);
60
61 if(time <= 0.0) time = 0.0f;
62 if(time >= duration) time = duration;
63
64 // Linear interpolation without smoothing.
65 return change * time / duration + base;
66}
67
68// Change frequency in discrete steps without smoothing.
69// Unlike the other two interpolation functions, this produces expected results
70// *most* of the time. Try playing with the step count parameter. Large values
71// start to cause wrong frequencies to be emitted (disregarding the static).
72float interpolate_discrete_step(
73 float time, float base, float change, float duration)
74{
75 assert(duration > 0.0);
76
77 if(time <= 0.0) time = 0.0f;
78 if(time >= duration) time = duration;
79
80 float steps = g_step_count; // From command-line.
81
82 float delta = change / steps;
83 float clock = duration / steps;
84 float count = 0.0f;
85
86 while(time >= clock * count) {
87 count = count + 1.0f;
88 }
89
90 return base + delta * count - delta;
91}
92
93// Generates the waveform.
94void waveform(
95 float* buffer, // Raw sample buffer.
96 int32_t samples, // Length of buffer.
97 float time, // Starting time value for this buffer.
98 uint64_t count, // Starting sample count for this buffer.
99 float ffreq, // Voice/mixer/stream frequency (float).
100 int32_t vfreq, // Voice/mixer/stream frequency (int).
101 int32_t which // Choice of interpolation function.
102 )
103{
104 float pi = (float)ALLEGRO_PI;
105 float pi2 = pi * 2.0f;
106 float dt = 1.0f / ffreq;
107
108 for(int32_t i = 0; i < samples; ++i) {
109 float pt = (float)i;
110 float ti = time + pt * dt;
111
112 float wave;
113 try_again:
114
115 if(which == 0) {
116 // Interpolate smoothly from 220 to 620 in 4 seconds.
117 wave = interpolate_sinusoidal_inout(ti, 220.0f, 400.0f, 4.0f) * pi2;
118 }
119 else if(which == 1) {
120 // Direct linear interpolation.
121 wave = interpolate_linear_tween(ti, 220.0f, 400.0f, 4.0f) * pi2;
122 }
123 else if(which == 2) {
124 // Discrete steps in frequency, no smooth changes.
125 // Note: high step counts create static.
126 wave = interpolate_discrete_step(ti, 220.0f, 400.0f, 4.0f) * pi2;
127 }
128 else if(which == 3) {
129 // Edgar Reynaldo.
130 wave = (220.0f + 100 * ti) * pi2;
131 }
132 else if(which == 5) {
133 // Edgar Reynaldo (with explicit stop point).
134 float mu = ti;
135 if(mu > 4.0f) mu = 4.0f;
136 wave = (220.0f + 100 * mu) * pi2;
137 }
138 else if(which == 4) {
139 // Sound Blaster.
140 wave = (440.0f + 220.0f * (std::sin(pi2 * (float)(count + i) /
141 (float)samples)) / 4.0f) * pi2;
142 }
143 else {
144 which = 0;
145 goto try_again;
146 }
147
148 // Print variables every 1/32 second.
149 if((count + i) % (vfreq / 32) == 0) {
150 printf("Time %f : Hertz %f : Freq %f : Arg %lf\n",
151 ti, wave, wave / pi2, wave * ti);
152 }
153
154 float va = std::sin(wave * ti);
155 buffer[i] = va;
156 }
157}
158
159int32_t main(int32_t argc, const char** argv)
160{
161 // Declare variables in advance because using 'goto' for cleanup.
162 bool success = false;
163
164 // Keep track of what items need cleaning up.
165 bool have_alleg = false;
166 bool have_audio = false;
167 bool have_voice = false;
168 bool have_mixer = false;
169 bool have_stream = false;
170
171 bool mixer_attached = false;
172 bool stream_attached = false;
173
174 // Set when time to exit.
175 bool done = false;
176
177 // Which interpolation function to use.
178 int32_t which = 0;
179
180 ALLEGRO_VOICE* voice = nullptr;
181 ALLEGRO_MIXER* mixer = nullptr;
182 ALLEGRO_AUDIO_STREAM* stream = nullptr;
183
184 // Voice/mixer/stream frequency. Same variable, 2 representations.
185 int32_t vfreq = 44100;
186 float ffreq = 44100.0f;
187
188 ALLEGRO_AUDIO_DEPTH idepth = ALLEGRO_AUDIO_DEPTH_INT16;
189 ALLEGRO_AUDIO_DEPTH fdepth = ALLEGRO_AUDIO_DEPTH_FLOAT32;
190 ALLEGRO_CHANNEL_CONF vchan = ALLEGRO_CHANNEL_CONF_2;
191 ALLEGRO_CHANNEL_CONF schan = ALLEGRO_CHANNEL_CONF_1;
192 int32_t fragments = 8; // Number of sample buffers.
193
194 // Samples per buffer. Same variable, 2 representations.
195 int32_t samples = 1024;
196 float fsamps = 1024.0f;
197
198 float time = 0.0f;
199 float dt = 1.0f / ffreq;
200
201 // Will store current sample value (1024 per second).
202 uint64_t count = 0;
203
204 // Parse command-line options.
205 if(argc >= 2) {
206 if(strcmp(argv[1], "sine") == 0) which = 0;
207 else if(strcmp(argv[1], "linear") == 0) which = 1;
208 else if(strcmp(argv[1], "step") == 0) {
209 which = 2;
210
211 // Parse step count parameter.
212 if(argc >= 3) {
213 std::string arg {argv[2]};
214 try {
215 std::size_t pos = 0;
216 g_step_count = (float)std::stoi(arg, &pos);
217
218 // Screen out bad parameters.
219 if(pos != arg.size() || g_step_count < 1.0f) {
220 throw std::exception {};
221 }
222 } catch(...) {
223 printf("Could not parse step count.\n");
224 goto cleanup;
225 }
226 }
227 }
228 else if(strcmp(argv[1], "edgar") == 0) which = 3;
229 else if(strcmp(argv[1], "edgar2") == 0) which = 5;
230 else if(strcmp(argv[1], "blaster") == 0) which = 4;
231 else if(strcmp(argv[1], "help") == 0) {
232 printf("Options: "
233 "sine | linear | step [count] | edgar | edgar2 | blaster | help.\n");
234 success = true;
235 goto cleanup;
236 }
237 else {
238 printf("Unrecognized option.\n");
239 goto cleanup;
240 }
241 }
242
243 // Initializations.
244 if(!al_install_system(ALLEGRO_VERSION_INT, atexit)) {
245 printf("Could not init Allegro.\n");
246 goto cleanup;
247 } else have_alleg = true;
248
249 if(!al_install_audio()) {
250 printf("Could not install audio.\n");
251 goto cleanup;
252 } else have_audio = true;
253
254 voice = al_create_voice(vfreq, idepth, vchan);
255 if(!voice) {
256 printf("Could not create hardware voice.\n");
257 goto cleanup;
258 } else have_voice = true;
259
260 mixer = al_create_mixer(vfreq, fdepth, vchan);
261 if(!mixer) {
262 printf("Could not create mixer.\n");
263 goto cleanup;
264 } else have_mixer = true;
265
266 mixer_attached = al_attach_mixer_to_voice(mixer, voice);
267 if(!mixer_attached) {
268 printf("Could not attach mixer to voice.\n");
269 goto cleanup;
270 }
271
272 stream = al_create_audio_stream(fragments, samples, vfreq, fdepth, schan);
273 if(!stream) {
274 printf("Could not create audio stream.\n");
275 goto cleanup;
276 } else have_stream = true;
277
278 stream_attached = al_attach_audio_stream_to_mixer(stream, mixer);
279 if(!stream_attached) {
280 printf("Could not attach audio stream to mixer.\n");
281 goto cleanup;
282 }
283
284 // Main loop.
285 while(!done) {
286 void* buffer = al_get_audio_stream_fragment(stream);
287 if(!buffer) {
288 al_rest(0.001f); // Don't be a timeslice hog.
289 continue;
290 }
291
292 // Generate waveform.
293 waveform((float*)buffer, samples, time, count, ffreq, vfreq, which);
294
295 // Advance time and sample count.
296 time += (dt * fsamps);
297 count += samples;
298
299 al_set_audio_stream_fragment(stream, buffer);
300 if(time >= 5.0f) done = true; // Quit after 5 seconds.
301 }
302
303 al_drain_audio_stream(stream);
304
305 success = true;
306 cleanup:
307
308 // Cleanup.
309 if(have_stream) {
310 al_detach_audio_stream(stream);
311 al_destroy_audio_stream(stream);
312 }
313
314 if(have_mixer) {
315 al_detach_mixer(mixer);
316 al_destroy_mixer(mixer);
317 }
318
319 if(have_voice) {
320 al_detach_voice(voice);
321 al_destroy_voice(voice);
322 }
323
324 if(have_audio) al_uninstall_audio();
325 if(have_alleg) al_uninstall_system();
326
327 if(success) {
328 printf("Everything worked.\n");
329 return EXIT_SUCCESS;
330 }
331
332 printf("Something went wrong.\n");
333 return EXIT_FAILURE;
334}
The whole thing is very strange, I'm positive the problem can't be the interpolation function(s). Edit: try $program step 10000. The results are nearly the same as linear (i.e., wrong), ignoring the static effects. |
MikiZX
Member #17,092
June 2019
|
I've tried the source as well. After changing it a little I managed to get it to work ok (?). The problem seems to be with this line: In the below source, you will see that there is a new global variable called 'sine' which progresses with each generated sample and according to the frequency calculated in the interpolate function. I'm not sure how to post the source code so sorry for this: Marked with '// THIS IS NEW' are the changes I did: #include <cstdint> #include <allegro5/allegro.h> float sine; // THIS IS NEW // Interpolates from one frequency to another within a duration. // Generates the waveform. for (int32_t i = 0; i < samples; ++i) { // Interpolate smoothly from 220 to 620 in 4 seconds. sine += frequency/44100.0f; // THIS IS NEW buffer[i] = va; } int32_t main(int32_t, const char**) sine = 0.0f; // THIS IS NEW // Declare variables in advance because using 'goto' for cleanup. |
Bluebird
Member #15,421
December 2013
|
IT WORKS! Thanks MikiZX, I can definitely say that your solution would never have occurred to me. One thing about it that concerned me though, was that your sine variable would saturate eventually if the playback went on long enough. I fixed this with this code: while(g_sine > pi2) g_sine -= pi2; Here's the whole program, working correctly now. If anyone can think of a use for it, have at it! 1// =============================================================================
2// This program generates a 5-second audio waveform that ramps smoothly up or
3// down for some number of seconds, and then holds at a single frequency for 1
4// second longer.
5//
6// The expected result is that the audio sound ramps up smoothly from one
7// frequency to another. Directly. Don't pass GO, don't collect $200.
8//
9// Author: Bluebird
10// License: MIT
11// =============================================================================
12
13#include <cstdint>
14#include <cstddef>
15#include <cstdlib>
16#include <cassert>
17#include <cstdio>
18#include <cstring>
19#include <cmath>
20
21#include <string>
22#include <exception>
23#include <stdexcept>
24#include <algorithm>
25
26#include <allegro5/allegro.h>
27#include <allegro5/allegro_audio.h>
28
29float g_step_count = 20.0f;
30float g_sine = 0.0f; // Used like an "accumulator" in the interpolation code.
31float g_start_frequency = 0.0f;
32float g_frequency_change = 0.0f;
33float g_interp_time = 0.0f;
34
35// Interpolates from one frequency to another within a duration.
36float interpolate_sinusoidal_inout(
37 float time, float base, float change, float duration)
38{
39 assert(duration > 0.0);
40 float pi = (float)ALLEGRO_PI;
41
42 if(time <= 0.0) time = 0.0f;
43 if(time >= duration) time = duration;
44
45 // This is a simple sinusoidal in/out interpolation.
46 return -change / 2.0f * (std::cos(pi * time / duration) - 1.0f) + base;
47}
48
49float interpolate_linear_tween(
50 float time, float base, float change, float duration)
51{
52 assert(duration > 0.0);
53
54 if(time <= 0.0) time = 0.0f;
55 if(time >= duration) time = duration;
56
57 // Linear interpolation without smoothing.
58 return change * time / duration + base;
59}
60
61// Change frequency in discrete steps without smoothing.
62float interpolate_discrete_step(
63 float time, float base, float change, float duration)
64{
65 assert(duration > 0.0);
66
67 if(time <= 0.0) time = 0.0f;
68 if(time >= duration) time = duration;
69
70 float steps = g_step_count; // From command-line.
71
72 float delta = change / steps;
73 float clock = duration / steps;
74 float count = 0.0f;
75
76 while(time >= clock * count) {
77 count = count + 1.0f;
78 }
79
80 return base + delta * count - delta;
81}
82
83// Generates the waveform.
84void waveform(
85 float* buffer, // Raw sample buffer.
86 int32_t samples, // Length of buffer.
87 float time, // Starting time value for this buffer.
88 uint64_t count, // Starting sample count for this buffer.
89 float ffreq, // Voice/mixer/stream frequency (float).
90 int32_t vfreq, // Voice/mixer/stream frequency (int).
91 int32_t which // Choice of interpolation function.
92 )
93{
94 float pi = (float)ALLEGRO_PI;
95 float pi2 = pi * 2.0f;
96 float dt = 1.0f / ffreq;
97
98 for(int32_t i = 0; i < samples; ++i) {
99 float pt = (float)i;
100 float ti = time + pt * dt;
101
102 float frequency;
103 float start = g_start_frequency;
104 float change = g_frequency_change;
105 float tt = g_interp_time;
106 try_again:
107
108 if(which == 0) {
109 // Interpolate smoothly from 220 to 620 in 4 seconds.
110 frequency = interpolate_sinusoidal_inout(ti, start, change, tt) * pi2;
111 }
112 else if(which == 1) {
113 // Direct linear interpolation.
114 frequency = interpolate_linear_tween(ti, start, change, tt) * pi2;
115 }
116 else if(which == 2) {
117 // Discrete steps in frequency, no smooth changes.
118 // Note: high step counts may create static or stuttering.
119 frequency = interpolate_discrete_step(ti, start, change, tt) * pi2;
120 }
121 else {
122 which = 0;
123 goto try_again;
124 }
125
126 // Generate the waveform, allowing for frequency interpolation.
127 g_sine = g_sine + frequency / ffreq;
128 while(g_sine > pi2) g_sine -= pi2; // Prevent saturation.
129 float va = std::sin(g_sine);
130
131 // Print variables every 1/32 second.
132 if((count + i) % (vfreq / 32) == 0) {
133 printf("Time %f : Freq %f : Sine %f\n",
134 ti, frequency / pi2, g_sine);
135 }
136
137 buffer[i] = va;
138 }
139}
140
141int32_t main(int32_t argc, const char** argv)
142{
143 // Declare variables in advance because using 'goto' for cleanup.
144 bool success = false;
145
146 // Keep track of what items need cleaning up.
147 bool have_alleg = false;
148 bool have_audio = false;
149 bool have_voice = false;
150 bool have_mixer = false;
151 bool have_stream = false;
152
153 bool mixer_attached = false;
154 bool stream_attached = false;
155
156 // Set when time to exit.
157 bool done = false;
158
159 // Which interpolation function to use.
160 int32_t which = 0;
161
162 ALLEGRO_VOICE* voice = nullptr;
163 ALLEGRO_MIXER* mixer = nullptr;
164 ALLEGRO_AUDIO_STREAM* stream = nullptr;
165
166 // Voice/mixer/stream frequency. Same variable, 2 representations.
167 int32_t vfreq = 44100;
168 float ffreq = 44100.0f;
169
170 ALLEGRO_AUDIO_DEPTH idepth = ALLEGRO_AUDIO_DEPTH_INT16;
171 ALLEGRO_AUDIO_DEPTH fdepth = ALLEGRO_AUDIO_DEPTH_FLOAT32;
172 ALLEGRO_CHANNEL_CONF vchan = ALLEGRO_CHANNEL_CONF_2;
173 ALLEGRO_CHANNEL_CONF schan = ALLEGRO_CHANNEL_CONF_1;
174 int32_t fragments = 8; // Number of sample buffers.
175
176 // Samples per buffer. Same variable, 2 representations.
177 int32_t samples = 1024;
178 float fsamps = 1024.0f;
179
180 float time = 0.0f;
181 float end_time = 0.0f;
182 float dt = 1.0f / ffreq;
183
184 // Will store current sample value (1024 per second).
185 uint64_t count = 0;
186
187 auto parse_float = [](
188 int32_t argc, const char** argv, int32_t index, float* out)
189 {
190 assert(out);
191 bool status = false;
192
193 if(argc >= index + 1) {
194 std::string arg {argv[index]};
195 try {
196 std::size_t pos = 0;
197 float result = (float)std::stof(arg, &pos);
198
199 // Screen out bad parameters.
200 if(pos != arg.size()) {
201 throw std::exception {};
202 }
203
204 status = true;
205 (*out) = result;
206 }
207 catch(...) {
208 status = false;
209 }
210 }
211
212 return status;
213 };
214
215 // Parse command-line options.
216 if(argc >= 2) {
217 // The index in the command-line at which common options start.
218 int32_t common_index = 2;
219
220 if(strcmp(argv[1], "sine") == 0) {
221 which = 0;
222 goto do_common_opts;
223 }
224 else if(strcmp(argv[1], "linear") == 0) {
225 which = 1;
226 goto do_common_opts;
227 }
228 else if(strcmp(argv[1], "step") == 0) {
229 which = 2;
230 bool result = parse_float(argc, argv, 2, &g_step_count);
231 if(!result) {
232 printf("Could not parse step count.\n");
233 goto cleanup;
234 }
235 g_step_count = std::max(g_step_count, 1.0f);
236 g_step_count = std::floor(g_step_count);
237
238 common_index = 3;
239 goto do_common_opts;
240 }
241 else if(strcmp(argv[1], "help") == 0) {
242 printf("Options:\n"
243 " sine [start_freq] [freq_change] [total_time]\n"
244 " linear [start_freq] [freq_change] [total_time]\n"
245 " step [count] [start_freq] [freq_change] [total_time]\n"
246 " help\n");
247 success = true;
248 goto cleanup;
249 }
250 else {
251 printf("Unrecognized option.\n");
252 goto cleanup;
253 }
254
255 // Parse common command-line options.
256 if(false) {
257 do_common_opts:
258 bool result;
259
260 result = parse_float(argc, argv, common_index + 0, &g_start_frequency);
261 if(!result) {
262 g_start_frequency = 220.0f;
263 }
264
265 result = parse_float(argc, argv, common_index + 1, &g_frequency_change);
266 if(!result) {
267 g_frequency_change = 400.0f;
268 }
269
270 result = parse_float(argc, argv, common_index + 2, &end_time);
271 if(!result) end_time = 5.0f;
272 end_time = std::max(end_time, 2.0f);
273 g_interp_time = end_time - 1.0f;
274 }
275 }
276 else {
277 printf("You must specify some option (pass \"help\").\n");
278 goto cleanup;
279 }
280
281 // Initializations.
282 if(!al_install_system(ALLEGRO_VERSION_INT, atexit)) {
283 printf("Could not init Allegro.\n");
284 goto cleanup;
285 } else have_alleg = true;
286
287 if(!al_install_audio()) {
288 printf("Could not install audio.\n");
289 goto cleanup;
290 } else have_audio = true;
291
292 voice = al_create_voice(vfreq, idepth, vchan);
293 if(!voice) {
294 printf("Could not create hardware voice.\n");
295 goto cleanup;
296 } else have_voice = true;
297
298 mixer = al_create_mixer(vfreq, fdepth, vchan);
299 if(!mixer) {
300 printf("Could not create mixer.\n");
301 goto cleanup;
302 } else have_mixer = true;
303
304 mixer_attached = al_attach_mixer_to_voice(mixer, voice);
305 if(!mixer_attached) {
306 printf("Could not attach mixer to voice.\n");
307 goto cleanup;
308 }
309
310 stream = al_create_audio_stream(fragments, samples, vfreq, fdepth, schan);
311 if(!stream) {
312 printf("Could not create audio stream.\n");
313 goto cleanup;
314 } else have_stream = true;
315
316 stream_attached = al_attach_audio_stream_to_mixer(stream, mixer);
317 if(!stream_attached) {
318 printf("Could not attach audio stream to mixer.\n");
319 goto cleanup;
320 }
321
322 printf("Total time: %f, starting frequency: %f, target frequency: %f.\n",
323 end_time, g_start_frequency, g_start_frequency + g_frequency_change);
324
325 // Main loop.
326 while(!done) {
327 void* buffer = al_get_audio_stream_fragment(stream);
328 if(!buffer) {
329 al_rest(0.001f); // Don't be a timeslice hog.
330 continue;
331 }
332
333 // Generate waveform.
334 waveform((float*)buffer, samples, time, count, ffreq, vfreq, which);
335
336 // Advance time and sample count.
337 time += (dt * fsamps);
338 count += samples;
339
340 al_set_audio_stream_fragment(stream, buffer);
341 if(time >= end_time) done = true; // Quit after some seconds.
342 }
343
344 al_drain_audio_stream(stream);
345
346 printf("Total time: %f, starting frequency: %f, target frequency: %f.\n",
347 end_time, g_start_frequency, g_start_frequency + g_frequency_change);
348
349 success = true;
350 cleanup:
351
352 // Cleanup.
353 if(have_stream) {
354 al_detach_audio_stream(stream);
355 al_destroy_audio_stream(stream);
356 }
357
358 if(have_mixer) {
359 al_detach_mixer(mixer);
360 al_destroy_mixer(mixer);
361 }
362
363 if(have_voice) {
364 al_detach_voice(voice);
365 al_destroy_voice(voice);
366 }
367
368 if(have_audio) al_uninstall_audio();
369 if(have_alleg) al_uninstall_system();
370
371 if(success) {
372 printf("Everything worked.\n");
373 return EXIT_SUCCESS;
374 }
375
376 printf("Something went wrong.\n");
377 return EXIT_FAILURE;
378}
One thing of note is that Edgar Reynaldo's SoundBlaster no longer sounds as it was originally intended to sound. I guess it depended on the wrong frequency behavior. Now if only I understood WHY it works ... I've been coding for years and I'm still not that good with math. |
GullRaDriel
Member #3,861
September 2003
|
One thing that's helping for sure: always make the maximum number of operations before dividing. a = ( b + c ) / d will keep precision longer than a = b/d + c/d The sine thing look like a variable range problem. MikiZX added a global sine value which he initialize on main, and then iterate before using it along interpolation. "Code is like shit - it only smells if it is not yours" |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Hey, I thought you would be interested. I made a simple sound generator you might like. Source code included (depends on Eagle and Allegro 5) 1#include "Eagle/backends/Allegro5Backend.hpp"
2#include "Eagle.hpp"
3
4
5/// Data(t) = Amplitude(t)*waveform(t)
6
7
8typedef float (*WAVEFORM) (float);
9typedef float (*VOLUMEFORM) (float);
10typedef float (*FREQFORM) (float);
11typedef float (*AUDIOFORM) (float);
12
13float SINWAVE (float pct) {
14 return sin(pct*2*M_PI);
15}
16float TRIANGLEWAVE (float pct) {
17 /// maps percent [0.0f,1.0f] to /\/ triangle wave
18 pct = fmod(pct , 1.0);
19 if (pct <= 0.25) {
20 return 0.0f + 1*pct*4.0f;
21 }
22 else if (pct <= 0.75) {
23 return 1.0f + (-2.0f*(pct - 0.25f)*2.0f);
24 }
25 else {
26 return -1.0f + 1.0f*(pct - 0.75f);
27 }
28 return 0.0f;/// Not reached
29}
30float SQUAREWAVE (float pct) {
31 pct = fmod(pct , 1.0);
32 return (pct <= 0.5)?1.0:-1.0;
33}
34
35
36float RAMP (float pct) {
37 pct = fmod(pct , 1.0f);
38 return pct*pct*pct;
39}
40
41float RAMP2 (float pct) {
42 pct = fmod(pct , 1.0f);
43 pct = 1.0 + 99.0f*pct;
44 return log10(pct)/2.0f;
45}
46
47float DAMP (float pct) {
48 pct = fmod(pct , 1.0f);
49 return (1.0f - pct)*(1.0f - pct);
50}
51
52float HALFVOL(float pct) {return 0.5f;}
53
54float RAMPANDDAMP (float pct) {
55 pct = fmod(pct , 1.0f);
56 if (pct <= 0.5f) {
57 return RAMP(pct*2.0f);
58 }
59 else {
60 return DAMP(pct*2.0f - 1.0f);
61 }
62 return 0.0f;/// Not reached
63}
64
65
66
67void waveform(float* buffer, int32_t samples, float time , float vfreq) {
68 for(int32_t i = 0; i < samples; ++i) {
69 float ti = time + i/vfreq;//pt * samples/vfreq;
70
71// buffer[i] = RAMP(fmod(ti,5.0f)/5.0)*TRIANGLEWAVE(440.0f*ti*2.0*M_PI);
72 buffer[i] = SINWAVE((220.0f + 220.0f*DAMP(ti/7.0f))*ti*2.0f*M_PI);
73 }
74}
75
76
77float FREQ220(float pct) {return 220.0f + 110.0f*RAMP(pct);}
78
79template<class Type>
80class Interp {
81public :
82 Type start;
83 Type finish;
84
85 Interp(Type begin , Type end) :
86 start(begin),
87 finish(end)
88 {
89
90 }
91// void operator(float pct);
92 Type operator()(float pct) {
93 pct = fmod(pct , 1.0f);
94 return start + (finish-start)*pct;
95 }
96};
97
98class Audio {
99 int buffer_fragment_count;///< Fragment count
100 int samples_per_buffer;///< Sample count
101 float samples_per_second;///< Mixer frequency
102 double secs_per_sample;///< Duration per sample
103
104 double sound_duration;
105
106 VOLUMEFORM volform;
107 WAVEFORM waveform;
108 FREQFORM freqform;
109
110 double volume;
111 double freq;
112
113public :
114 Audio() :
115 buffer_fragment_count(8),
116 samples_per_buffer(1024),
117 samples_per_second(44100.0f),
118 secs_per_sample(1.0/samples_per_second),
119 sound_duration(5.0),
120 volform(HALFVOL),
121 waveform(SINWAVE),
122 freqform(FREQ220),
123 volume(0.0),
124 freq(220.0)
125 {}
126
127
128// void FillBuffer(float* buf , float audiotime);
129 void FillBuffer(float* buf , float audiotime) {
130 double ti = audiotime;
131 for (unsigned int i = 0 ; i < (unsigned int)samples_per_buffer ; ++i) {
132// buf[i] = waveform(freqform(ti/sound_duration)*ti*2.0*M_PI/sound_duration);
133// buf[i] = waveform(220.0f*ti*2.0*M_PI);
134// buf[i] = waveform(Interp<float>(220.0f , 330.0f)(ti/sound_duration)*ti*2.0*M_PI);
135/// buf[i] = Interp<float>(SINWAVE(220.0f*ti*2.0f*M_PI/3.0f) , SQUAREWAVE(220.0f*ti*2.0f*M_PI/3.0f))(ti/3.0f);
136 buf[i] = volume*waveform(freq*2.0*M_PI*ti);
137 ti += secs_per_sample;
138 }
139 }
140 void SetVolume(double pct) {
141 if (pct < 0.0) {pct = 0.0;}
142 if (pct > 1.0) {pct = 1.0;}
143 volume = pct;
144 }
145 void SetFrequency(double hz) {
146 freq = hz;
147 }
148 void SetWaveForm(WAVEFORM wf) {
149 waveform = wf;
150 }
151};
152
153
154#include <iostream>
155
156
157int main(int argc , char** argv) {
158
159 (void)argc;
160 (void)argv;
161
162
163 SendOutputToFile("sfx.log.txt" , "" , false);
164 EagleLogger::RemoveOutputStream(std::cout);
165
166 Allegro5System* a5sys = GetAllegro5System();
167
168 if (a5sys->Initialize(EAGLE_STANDARD_SETUP) != EAGLE_STANDARD_SETUP) {
169 return -1;
170 }
171
172 ALLEGRO_VOICE* voice = al_create_voice(44100 , ALLEGRO_AUDIO_DEPTH_INT16 , ALLEGRO_CHANNEL_CONF_2);
173 ALLEGRO_MIXER* mixer = al_create_mixer(44100 , ALLEGRO_AUDIO_DEPTH_INT16 , ALLEGRO_CHANNEL_CONF_2);
174 ALLEGRO_AUDIO_STREAM* stream = al_create_audio_stream(8 , 1024 , 44100 , ALLEGRO_AUDIO_DEPTH_FLOAT32 , ALLEGRO_CHANNEL_CONF_1);
175
176 if (!voice || !mixer || !stream) {
177 EagleLog() << "Setup error" << std::endl;
178 if (!voice) {
179 EagleWarn() << "Failed to setup voice" << std::endl;
180 return -1;
181 }
182 if (!mixer) {
183 EagleWarn() << "Failed to setup mixer" << std::endl;
184 return -2;
185 }
186 if (!stream) {
187 EagleWarn() << "Failed to setup stream" << std::endl;
188 return -3;
189 }
190
191 }
192
193 bool mixer_attached = al_attach_mixer_to_voice(mixer, voice);
194
195 if (!mixer_attached) {
196 EagleWarn() << "Failed to attach mixer to voice" << std::endl;
197 return -4;
198 }
199
200 bool stream_attached = al_attach_audio_stream_to_mixer(stream, mixer);
201
202 if (!stream_attached) {
203 EagleWarn() << "Failed to attach stream to mixer" << std::endl;
204 return -5;
205 }
206
207 if (al_get_audio_stream_fragments(stream) != 8) {
208 EagleLog() << "Didn't get 8 fragments.\n";
209 }
210 if (al_get_audio_stream_channels(stream) != ALLEGRO_CHANNEL_CONF_1) {
211 EagleLog() << "Not a mono stream!\n";
212 }
213 if (!al_get_audio_stream_attached(stream)) {
214 EagleLog() << "Stream not attached!\n";
215 }
216 if (al_get_audio_stream_frequency(stream) != 44100) {
217 EagleLog() << "Not mixing at 44100 HZ!\n";
218 }
219
220 int sw = 800;
221 int sh = 600;
222
223 EagleGraphicsContext* win = a5sys->CreateGraphicsContext("Main window" , sw , sh , EAGLE_OPENGL | EAGLE_WINDOWED);
224
225 WidgetHandler gui(win);
226 gui.SetWidgetArea(WIDGETAREA(Rectangle(0 , 0 , sw , sh) , false));
227 gui.SetupBuffer(sw,sh , win);
228
229 RelativeLayout rlayout;
230 gui.SetRootLayout(&rlayout);
231
232 Slider sl1,sl2,sl3;
233 rlayout.AddWidget(&sl1 , LayoutRectangle(0.05 , 0.2 , 0.05 , 0.5));
234 rlayout.AddWidget(&sl2 , LayoutRectangle(0.35 , 0.2 , 0.05 , 0.5));
235 rlayout.AddWidget(&sl3 , LayoutRectangle(0.65 , 0.2 , 0.05 , 0.5));
236
237 EagleFont* f = win->DefaultFont();
238
239 BasicText lbl1,lbl2,lbl3;
240 lbl1.SetText("VOL" , f);
241 lbl2.SetText("HZ" , f);
242 lbl3.SetText("WAVE" , f);
243
244 rlayout.AddWidget(&lbl1 , LayoutRectangle(0.05 , 0.02 , 0.3 , 0.1));
245 rlayout.AddWidget(&lbl2 , LayoutRectangle(0.35 , 0.02 , 0.3 , 0.1));
246 rlayout.AddWidget(&lbl3 , LayoutRectangle(0.65 , 0.02 , 0.3 , 0.1));
247
248 BasicText t1,t2,t3;
249 t1.SetText("" , f);
250 t2.SetText("" , f);
251 t3.SetText("" , f);
252
253 rlayout.AddWidget(&t1 , LayoutRectangle(0.05 , 0.12 , 0.3 , 0.25));
254 rlayout.AddWidget(&t2 , LayoutRectangle(0.35 , 0.12 , 0.3 , 0.25));
255 rlayout.AddWidget(&t3 , LayoutRectangle(0.65 , 0.12 , 0.3 , 0.25));
256
257
258
259 Audio audio;
260
261 double vol = 0.5;
262 double freq = 220.0f;
263 WAVEFORM wf = SINWAVE;
264
265 audio.SetVolume(vol);
266 audio.SetFrequency(freq);
267 audio.SetWaveForm(wf);
268
269 sl1.SetPercent(0.5);
270 sl2.SetPercent(0.25);
271 sl3.SetPercent(0.0);
272
273 t1.SetText("0.5");
274 t2.SetText("220.0");
275 t3.SetText("SINWAVE");
276
277 double start = ProgramTime::Now();
278 double time = start;
279 double audiotime = 0.0;
280
281 bool quit = false;
282 bool redraw = true;
283
284 EagleEventHandler* sysqueue = a5sys->GetSystemQueue();
285
286 sysqueue->ListenTo(&gui);
287
288 a5sys->GetSystemTimer()->Start();
289
290
291 while(!quit) {
292 if (redraw) {
293 win->Clear();
294 gui.Display(win , 0 , 0);
295 win->FlipDisplay();
296 }
297
298 do {
299 EagleEvent e = a5sys->WaitForSystemEventAndUpdateState();
300 if (e.type == EAGLE_EVENT_TIMER) {
301 /// Check if the audio stream is empty
302 void* buffer = 0;
303 while ((buffer = al_get_audio_stream_fragment(stream))) {
304 EagleLog() << "FillingBuffer...";
305 audio.FillBuffer((float*)buffer , audiotime);
306 audiotime += 1024.0/44100.0;
307 al_set_audio_stream_fragment(stream , buffer);
308 }
309 gui.Update(e.timer.eagle_timer_source->SPT());
310 redraw = true;
311 }
312 if (e.type == EAGLE_EVENT_DISPLAY_CLOSE) {
313 quit = true;
314 }
315 if (e.type == EAGLE_EVENT_KEY_DOWN && e.keyboard.keycode == EAGLE_KEY_ESCAPE) {
316 quit = true;
317 }
318 if (e.type == EAGLE_EVENT_WIDGET) {
319 WIDGET_EVENT_DATA d = e.widget;
320 if (d.from == &sl1 || d.from == &sl2 || d.from == &sl3) {
321 if (d.topic == TOPIC_SLIDER) {
322 if (d.msgs == SLIDER_VALUE_CHANGED) {
323 if (d.from == &sl1) {
324 vol = sl1.GetPercent();
325 audio.SetVolume(vol);
326 t1.SetText(StringPrintF("VOL %1.4lf" , vol));
327 }
328 if (d.from == &sl2) {
329 freq = 110.0 + 440.0*sl2.GetPercent();
330 audio.SetFrequency(freq);
331 t2.SetText(StringPrintF("HZ %1.4lf" , freq));
332 }
333 if (d.from == &sl3) {
334 double val = sl3.GetPercent();
335 if (val <= 0.34) {
336 wf = SINWAVE;
337 t3.SetText("SINWAVE");
338 }
339 else if (val <= 0.68) {
340 wf = TRIANGLEWAVE;
341 t3.SetText("TRIWAVE");
342 }
343 else {
344 wf = SQUAREWAVE;
345 t3.SetText("SQRWAVE");
346 }
347 audio.SetWaveForm(wf);
348 }
349 }
350 }
351 }
352 }
353 else {
354 gui.HandleEvent(e);
355 }
356 } while (sysqueue->HasEvent());
357 }
358
359
360
361 return 0;
362}
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 |
|