Audio waveform with unexpected frequency behavior
Bluebird

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.

#SelectExpand
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

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.

MikiZX

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).
Not really much help but who knows maybe it gives you some ideas as to what to try next.

Edgar Reynaldo

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

media player
https://d1cxvcw9gjxu2x.cloudfront.net/attachments/612091

Bluebird

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").

#SelectExpand
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

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:
float va = std::sin(wave * ti);

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 <cstddef>
#include <cstdlib>
#include <cassert>
#include <cstdio>
#include <cmath>

#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>

float sine; // THIS IS NEW

// Interpolates from one frequency to another within a duration.
float interpolate(float time, float base, float change, float duration)
{
assert(duration > 0.0);
auto pi = (float)ALLEGRO_PI;

if (time <= 0.0) return base;
if (time >= duration) return base + change;

return -change / 2.0f * (std::cos(pi * time / duration) - 1.0f) + base;
}

// Generates the waveform.
void waveform(
float* buffer, int32_t samples, float time, uint64_t count, float vfreq)
{
auto pi = (float)ALLEGRO_PI;
auto pi2 = pi * 2.0f;
auto dt = 1.0f / vfreq;

for (int32_t i = 0; i < samples; ++i) {
float pt = (float)i;
float ti = time + pt * dt;

// Interpolate smoothly from 220 to 620 in 4 seconds.
float frequency = interpolate(ti, 220.0f, 400.0f, 4.0f) *pi2;

sine += frequency/44100.0f; // THIS IS NEW
float va = std::sin(sine);

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.
bool success = false;

...

Bluebird

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!

#SelectExpand
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

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.

Edgar Reynaldo

Hey, I thought you would be interested. I made a simple sound generator you might like.

Get it here (SoundFoX1.zip)

Source code included (depends on Eagle and Allegro 5)

#SelectExpand
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}

Thread #617880. Printed from Allegro.cc