Home > Back-end >  Why is this sine tone I generated messy (in .wav file)?
Why is this sine tone I generated messy (in .wav file)?

Time:08-17

So, I have been trying to make a WAV format file from scratch in C. The thing is, I can't wrap my head around as to why the sine tone is this noisy (see the screenshot below). I thought there's something wrong with my sine generating sequence, but I have seen a video in YouTube (https://www.youtube.com/watch?v=qqjvB_VxMRM) where the guy does exactly what I did and got a decent sine wave.

Here's the code:

int main() {
    FILE *fptr;
    char path[] ="sin400.wav";
    fptr        =fopen(path, "r "); // Open the file
    if (fptr == NULL) {
        fclose(fptr);
        printf("Creating new file %s\n", path);
        fptr = fopen(path, "w ");
    }
    HEADER_t header={"RIFF", chunkSize, "WAVE"};
    FMT_t format            =   { "fmt ", 16, 1, CHANNELS, SAMPLE_RATE, BYTE_RATE, BLOCK_ALIGN, 8 * bytes_per_sample };
    fwrite(&header, sizeof(header), 1, fptr);
    printf("test1\n");
    fwrite(&format, sizeof(format), 1, fptr);
    printf("test2\n");
// ==== Data ====
    float amp   =   0.5 * 32767;
    int bufSize =   SAMPLE_RATE * DURATION;
    float T     =   (2.0 * M_PI) * freq / SAMPLE_RATE;
    float angle =   0;
    short buf[SAMPLE_RATE * DURATION];
    printf("T: %f\n", T);

    // Storing the sine wave to *buf

    // Generating sine
    for (int i = 0; i < bufSize; i  ) {
        buf[i] = (amp * sin(angle));
        angle  = T;
    }
    fclose(test);

    DATA_t data     =   { "data", SUBCHUNK2_size, buf};
    fwrite(&data, sizeof(data), 1, fptr); // Writing the data to the file
    fclose(fptr); // Close the file
}

The structures:

// ======== Structures ========

// RIFF Header
typedef struct HEADER_s{
    char chunkID[4];
    uint32_t chunkSize;
    char format[4];
}HEADER_t;

// FMT (Format) Subchunk
typedef struct FMT_s{
    char subChunk1ID[4];
    uint32_t subChunk1Size;
    uint16_t format;
    uint16_t channel;
    uint32_t smplRate;
    uint32_t byteRate;
    uint16_t blockAlign;
    uint16_t bitsPerSample;
}FMT_t;

// Data Subchunk
typedef struct DATA_s{
    char subChunk2ID[4];
    uint32_t subChunk2Size;
    uint16_t *data;
}DATA_t;

Constants defined:

#define M_PI 3.14159265358979323846
const float freq = 440.f;
#define SUBCHUNK1_size 16 // It is 16 bytes for PCM
#define DURATION 2 // Duration of the audio in seconds
#define SAMPLE_RATE 44100 // This rate determines the quality of the sound, unit: Hz (44100 Hz is the sampling rate in CD)
#define CHANNELS 1 // Mono
#define bytes_per_sample 2 // It is 2 for PCM
#define SUBCHUNK2_size DURATION*SAMPLE_RATE*CHANNELS*bytes_per_sample
#define BYTE_RATE SAMPLE_RATE*CHANNELS*bytes_per_sample
#define BLOCK_ALIGN CHANNELS*bytes_per_sample
const int chunkSize=4 (8 SUBCHUNK1_size) (8 SUBCHUNK2_size);

I have used (amp * sin(angle)) to get the sine values. Angle is offset in every iteration by T: angle = T

where T = (2 * M_PI) * freq / SAMPLE_RATE

The output waveform is:

Output

I have no idea why this is happening. The wave is immaculate for certain sample ranges, and goes haywire everywhere else. :(

CodePudding user response:

Now that I reviewed your comment about testing out the YouTube program and the "wav" file it created, I thought I would share the code version I derived from reviewing the C code, utilizing your code above. I did not have the information on your structures, so I decided to simplify things as the layout of the "wav" file is basically, header information followed by the data portion (in this case a sine wave).

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

int main()
{
    /* Data */
    int   x_size        = 16;
    short x_compression = 1;
    short x_channels    = 1;
    int   x_samplerate  = 44100;
    int   duration      = 2;
    short x_bitdepth    = 16;
    short x_block       = 2;
    float amp           = 0.5 * 32767;
    float freq          = 440.0;                    /* A above middle C */
    int   bufSize       = x_samplerate * duration;
    float T             = (2.0 * M_PI) * freq / x_samplerate;
    float angle         = 0;
    int   x_byterate    = x_samplerate * x_bitdepth / 8;
    char  x_header[17];
    char  x_data[9];

    short buf[x_samplerate * duration];

    FILE *fptr;

    fptr = fopen("sin400.wav", "r ");               // Open the file

    if(fptr == NULL)
    {
        //fclose(fptr);                             /* If fptr is NULL, trying to close it will cause a segmentation fault */
        printf("Creating new file sine400.wav\n");
        fptr=fopen("sin400.wav", "w ");
    }

    strcpy(x_header, "RIFF----WAVEfmt ");
    strcpy(x_data, "data----");

    fwrite(x_header, 1, 16, fptr);                  /* Header               */
    fwrite(&x_size, 4, 1, fptr);                    /* Size                 */
    fwrite(&x_compression, 2, 1, fptr);             /* Compression          */
    fwrite(&x_channels, 2, 1, fptr);                /* Channels             */
    fwrite(&x_samplerate, 4, 1, fptr);              /* Sample rate          */
    fwrite(&x_byterate, 4, 1, fptr);                /* Byte rate            */
    fwrite(&x_block, 2, 1, fptr);                   /* Block                */
    fwrite(&x_bitdepth, 2, 1, fptr);                /* Bit depth            */
    fwrite(x_data, 1, 8, fptr);                     /* Data block start ID  */

    // Storing the sine wave to *buf

    // Generating sine
    for(int i = 0; i < bufSize; i  )
    {
        buf[i] = (amp*sin(angle));
        angle  = T;
    }

    fwrite(buf, 1, sizeof(buf), fptr);              // Writing the data to the file
    fclose(fptr);                                   // Close the file
}

Instead of structures, I just used string functionality to keep things simple. When I listened to the "wav" files created by both the YouTube C program and the C program I concocted, they sounded the same to my ear (I don't Audacity). When I compared the raw byte data in the two files, again they matched up.

@Una:~/C_Programs/Console/Sinewave/bin/Release$ hexdump waveform.wav 
0000000 4952 4646 b134 0002 4157 4556 6d66 2074
0000010 0010 0000 0001 0001 ac44 0000 5888 0001
0000020 0002 0010 6164 6174 b110 0002 0000 0402
0000030 0800 0bf7 0fe1 13bb 1782 1b30 1ec4 2238
0000040 258b 28b7 2bba 2e92 313a 33b1 35f4 3801
0000050 39d6 3b70 3ccf 3df0 3ed3 3f77 3fdb 3fff
0000060 3fe2 3f86 3ee9 3e0d 3cf3 3b9b 3a07 3839
0000070 3633 33f6 3185 2ee2 2c0f 2911 25e9 229b
0000080 1f2a 1b9a 17ee 142a 1052 0c69 0874 0476
0000090 0074 fc73 f874 f47c f090 ecb4 e8eb e539
00000a0 e1a3 de2a dad4 d7a3 d49b d1bf cf11 cc94
00000b0 ca4b c838 c65d c4bc c356 c22e c144 c098

@Una:~/C_Programs/Console/Makewave/bin/Release$ hexdump sin400.wav 
0000000 4952 4646 2d2d 2d2d 4157 4556 6d66 2074
0000010 0010 0000 0001 0001 ac44 0000 5888 0001
0000020 0002 0010 6164 6174 2d2d 2d2d 0000 0402
0000030 0800 0bf7 0fe1 13bb 1782 1b30 1ec4 2238
0000040 258b 28b7 2bba 2e92 313a 33b1 35f4 3801
0000050 39d6 3b70 3ccf 3df0 3ed3 3f77 3fdb 3fff
0000060 3fe2 3f86 3ee9 3e0d 3cf3 3b9b 3a07 3839
0000070 3633 33f6 3185 2ee2 2c0f 2911 25e9 229b
0000080 1f2a 1b9a 17ee 142a 1052 0c69 0874 0476
0000090 0074 fc73 f874 f47c f090 ecb4 e8eb e539
00000a0 e1a3 de2a dad4 d7a3 d49b d1bf cf11 cc94
00000b0 ca4b c838 c65d c4bc c356 c22e c144 c098

The "waveform.wav" file was the YouTube file and "sin400.wav" was the file generated by the C program.

Go ahead and give that a try and see if it meets the spirit of your project.

CodePudding user response:

The sample data is not being written to a file. The data member in DATA_t is just a pointer to the sample data. The filled in DATA_t is written to the file by fwrite(&data, sizeof(data), 1, fptr);. That writes a "data" chunk ID, a chunk size, and a 4-or-8-byte pointer value to the file. The code should write the "data" chunk ID, the chunk size, and the actual array of sample data to the file.

I suggest the following changes:

  1. Open the output file in "wb" mode instead of "r " mode so that it is created in binary mode with zero length initially:

        fptr        =fopen(path, "r "); // Open the file
    
  2. Change the data member of DATA_t to be a flexible array member:

    // Data Subchunk
    typedef struct DATA_s{
        char subChunk2ID[4];
        uint32_t subChunk2Size;
        uint16_t data[];  // flexible array member
    }DATA_t;
    
  3. Initialize DATA_t data as follows:

        DATA_t data     =   { "data", SUBCHUNK2_size };
    
  4. Write the data chunk header as follows:

        fwrite(&data, offsetof(DATA_t, data), 1, fptr); // Writing the data chunk header to the file
    

    (Note: offsetof is defined by #include <stddef.h>.)

  5. Write the sample data to the file:

        fwrite(buf, data.subChunk2Size, 1, fptr); // Writing sample data to the file
    

You should also check the results of all the calls to fwrite and fclose.

  • Related