0
$\begingroup$

I wrote a very basic C program to play a sine of a user-specified frequency. In the interest of portability, I have it spit values directly to stdout, so hopefully you can reproduce my problem on your own machine. This is the code I wrote:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define RATE 48000
#define TWOPI (3.14159 * 2.0)

int main(int argc, char** argv) {
        float frequency = atof(argv[1]);
        float phase = 0.0;
        float increment = TWOPI/RATE;
        int16_t sample;
        while (1) {
                sample = sin(phase) * 32767;
                putchar(sample & 255);
                putchar(sample >> 8);
                phase += increment * frequency;
        }
        return 0;
}

It can be compiled with:

cc sine.c -lm

and played with

./a.out 440.0 | sox -r 48000 -c 1 -t s16 - -d

That is: 16 bit mono audio at a sampling rate of 48,000 Hz.

I've tested this on both OpenBSD and MacOS and have noticed the same behavior: at around 6 seconds, the pitch of the wave jumps slightly. This happens again around 12 seconds. Further changes will take place as the wave continues to play. I have also replaced the sin() while loop with a prebuilt wavetable of a sine accessed through linear interpolation, only to notice the same problems.

Any ideas what might be off here?

$\endgroup$
4
  • $\begingroup$ never access beyond argv[0] without first checking argc to assure the desired command line parameter actually exists $\endgroup$ Commented Dec 23, 2017 at 15:56
  • $\begingroup$ a literal value is a double unless the value contains a trailing f. Then it is a float value. The posted code contains several such literals, and is dividing a double value by a float value, which results in the compiler displaying a warning about this conversion between types $\endgroup$ Commented Dec 23, 2017 at 15:58
  • $\begingroup$ the posted code did not check argc, so when the user does not enter a command line parameter, the program seg faults at the call to atof() $\endgroup$ Commented Dec 23, 2017 at 16:06
  • $\begingroup$ the function: atof() has misleading name It actually returns a double, not a float $\endgroup$ Commented Dec 23, 2017 at 16:14

2 Answers 2

1
$\begingroup$

You are using 32-bit floats, and not resetting the phase by subtracting 2 pi. That means the phase will eventually climb up to where the sin() function's phase unwrapping algorithm doesn't have enough bits of quantization, or valid mantissa, left over afterward unwrapping.

Add this:

if (phase > M_PI) { phase -= 2.0 * M_PI; }

inside your loop somewhere.

$\endgroup$
2
  • $\begingroup$ That's very interesting. I was under the impression that 32-bit floats were good enough for most things audio-related. Your solution works, as does changing the phase and increment to double types. $\endgroup$
    – Ian Martin
    Commented Dec 24, 2017 at 9:35
  • $\begingroup$ Though would the latter method run into the same problem eventually? $\endgroup$
    – Ian Martin
    Commented Dec 24, 2017 at 9:42
0
$\begingroup$

the following proposed code:

  1. incorporates the comments to the question.
  2. cleanly compiles

and now, the proposed code:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define RATE 48000.0
#define TWOPI (3.14159 * 2.0)

int main(int argc, char** argv)
{
    if( 2 != argc )
    {
        fprintf( stderr, "USAGE %s <frequency>\n", argv[0] );
        exit( EXIT_FAILURE );
    }


    double frequency = atof(argv[1]);
    double phase = 0.0;
    double increment = TWOPI/RATE;
    double sample;

    while (1)
    {
            sample = sin(phase) * 32767.0f;
            printf( "%lf\n", sample );
            phase += increment * frequency;
    }
    return 0;
}

When run with: ./myprogram 1000 the result is:

0.000000
4276.948149
8480.716652
12539.377987
16383.487450
19947.271373
23169.752528
25995.793463
28377.039914
30272.748164
31650.482173
32486.668571
32767.000000
32486.679920
31650.504678
30272.781438
28377.083389
25995.846395
23169.814011
19947.340355
16383.562751
12539.458318
8480.800639
4277.034355
0.086950
-4276.861942
-8480.632664
-12539.297655
-16383.412149
-19947.202391
-23169.691045
-25995.740531
-28376.996439
-30272.714889
-31650.459669
-32486.657221
-32767.000000
-32486.691269
-31650.527182
-30272.814712
-28377.126864
-25995.899326
-23169.875494
-19947.409337
-16383.638052
-12539.538650
-8480.884627
-4277.120561
-0.173900
4276.775736
8480.548677
12539.217324
16383.336847
19947.133408
23169.629562
25995.687599

and so on.

Where is the jumps you ask about?

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.