Search code examples
cfileformatwav

program in C which reads data of sound and checks if the given data fulfills the prerequisites of a wav file


The demand of the project is to write a program in C which reads data of sound only with getchar from the input according to wav format/template.
The program must check if the given data is correct. When the first mistake is detected an appropriate message appears and the program stops. The use of arrays, functions, pointers and mathematical libraries is forbidden. Also it checks if we have insufficient data or bad file size.

The wave file format:
http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf http://www.topherlee.com/software/pcm-tut-wavformat.html

example I thought this code for reading numbers:

while ( (ch = getchar()) != '\n') 
{
    if (ch >= '0' && ch <= '9')
    {
        bytes_sec = 10 * bytes_sec  + (ch - '0');
    }
}
fprintf(stderr, "bytes/sec:%d\n", bytes_sec);

but it isn't correct because we want the number to be saved as one byte.

and for characters I thought this code:

flag = 0;
fores = 0;

while ( ( (ch=getchar()) !='\n') && (flag==0)  ) {
    fores = fores + 1;
    if  (fores == 1) {
        if (ch != 'R')
        {
            flag = 1;
        }
    }
    else if (fores == 2) {
        if (ch != 'I')
            {
                flag = 1;
            }
    }
    else if (fores == 3) {
        if (ch!='F') {
                flag = 1;
            }
    }
    else {
        if ((fores != 4) || (ch != 'F')) {
            flag = 1;
        }
    }
}

if (flag == 1) {
    fprintf(stderr, "Error! \"RIFF\" not found\n");
    return 0;
}

Also I didn't understand in which form (binary, hex, decimal) the data is given (I know that data in wav format is in binary but I still can't understand what exactly is given as data and how - form and if it is given as a single piece or separately).
Finally the fact that we can not enter in the terminal data that do not correspond to printable characters really confuses me.

Let's say we have the contents of a legal wav file (hex):

0000000 52 49 46 46 85 00 00 00 57 41 56 45 66 6d 74 20
0000020 10 00 00 00 01 00 01 00 44 ac 00 00 88 58 01 00
0000040 02 00 10 00 64 61 74 61 58 00 00 00 00 00 d4 07
0000060 a1 0f 5e 17 04 1f 8a 26 ea 2d 1c 35 18 3c d7 42
0000100 54 49 86 4f 69 55 f6 5a 27 60 f8 64 63 69 64 6d
0000120 f7 70 18 74 c5 76 fa 78 b6 7a f6 7b b9 7c ff 7c
0000140 c8 7c 12 7c e0 7a 33 79 0b 77 6c 74 58 71 d1 6d
0000160 dd 69 7e 65 b8 60 92 5b 0f 56 36 50 0c 4a 97 43
0000200 df 3c ea 35 45 78 74 72 61 44 61 74 61
0000215

Ι thought to convert it from hex to decimal and then use a notepad to create a data file in order to use the command fopen(fname, "rb") but then I will have to use pointer which is forbidden. So I still haven't understand how the program will get prices/values at the input.

Also after the suggestions of @AndreasWenzel I came up with this code for little-endian (note: the condition in while is incorrect, it's been chosen in order to be easy to check in a C compiler):

#include <stdio.h>

int ch, sample_rate, help, fores, sum, i;
int main()
{
    fores = 0;
    sample_rate = 0;
    
    while ( (ch = getchar()) != '\n' )
    {
            help = (ch - '0');
            fprintf(stderr, "help:%d\n", help);
            if (help == 1)
            {
                sum = 1;
                for (i=1 ; i<=fores ; i++)
                {
                    sum = sum*2;
                }
                sample_rate = sample_rate + (sum);
                fprintf(stderr, "sample:%d\n", sample_rate);
            }
            fores = fores + 1;
    }
fprintf(stderr, "sample rate:%d\n", sample_rate);
}

If we have as input the 10100100(binary)=164(decimal) it will print 37(decimal). Is it correct?


Solution

  • See this link for an explanation on how integers are represented in memory when stored in binary. Note that for this task, you do not have to read about how signed integers are represented in memory (you only need to know about unsigned), and you also don't have to read about how floating-point numbers are represented in memory.

    In a RIFF file (such as a .wav file), most data is not stored in ASCII, but in binary. Only very few things in a RIFF file are stored in ASCII. For example, the (sub-)chunk headers each contain a 4-byte ID, such as RIFF, fmt , which is stored in ASCII. But numbers will almost never be stored in the ASCII encoding of the characters '0' to '9', as your code seems to assume.

    When reading a binary file, the while condition

    (ch=getchar()) !='\n'

    does not make sense. Such an expression only makes sense with line-delimited text input. In a binary file, the value '\n', which is the value of the ASCII encoding of the newline character, has no special meaning. It is just a byte value like any other, which may coincidentally occur in the binary file, for example in the binary representation of a number.

    Therefore, in order to check the RIFF ChunkID, your while loop should instead always read exactly 4 bytes from the file. It should not continue reading until it finds a '\n', as it does now.

    In order to read the subsequent ChunkSize value, which is 4 bytes long, you should read exactly 4 bytes again. You should also have a variable of type uint_least32_t, which is an unsigned integer and is guaranteed to be at least 32 bits in length. This ensures that the variable is sufficient in size to store the ChunkSize. You can then read one byte at a time using getchar and then calculate the whole number using the values of the individual bytes. See the above link about how integers are represented in computer memory, in order to understand how to calculate a larger number from individual byte values. In accordance with these rules on homework questions, I will not provide a solution to this problem, unless you specifically ask for it. Until then, I will only provide the following hints:

    • You must take into account that the number is stored in little-endian byte ordering.
    • When reading with getchar, you should store the result in an int or an unsigned char, not signed char or a char (which is signed on most platforms). Otherwise, the value of the variable will be negative, if the byte has a value larger than 127. Such negative values are harder to work with.

    Note that if you are using #include <windows.h> (and you are on a platform which has this header), you can use the typedef DWORD instead of uint_least32_t. If you are using uint_least32_t, you will have to #include <stdint.h>.


    EDIT: Now that you have solved the problem yourself, I will provide my own alternate solution. However, the task forbids the use of arrays, pointers and user-defined functions. On the other hand, in my opinion, all solutions to the problem which respect these restrictions will be messy and contain a large amount of unnecessary code duplication. Therefore, I have used them in my solution, which does not respect these restrictions:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <inttypes.h>
    #include <limits.h>
    
    //This will print an error message if you attempt to compile this 
    //on a platform on which there are not 8 bits per byte.
    #if CHAR_BIT != 8
    #error "This program assumes 8 bits per byte."
    #endif
    
    void VerifyLabel( const char *label );
    uint_fast32_t ReadNumber( int num_bytes, const char *fieldname );
    
    int main( void )
    {
        uint_fast32_t temp;
    
        VerifyLabel( "RIFF" );
    
        ReadNumber( 4, "ChunkSize" );
    
        VerifyLabel( "WAVE" );
    
        VerifyLabel( "fmt " );
    
        temp =
            ReadNumber( 4, "Subchunk1Size" );
    
        if ( temp != 16 )
        {
            fprintf( stderr, "Error: Expected Subchunk1Size to be 16!\n" );
            exit( EXIT_FAILURE );
        }
    
        temp =
            ReadNumber( 2, "AudioFormat" );
    
        if ( temp != 1 )
        {
            fprintf( stderr, "Error: Expected AudioFormat to be 1 (PCM)!\n" );
            exit( EXIT_FAILURE );
        }
    
        temp =
            ReadNumber( 2, "NumChannels" );
    
        if ( temp != 1 && temp != 2 )
        {
            fprintf( stderr, "Error: Expected NumChannels to be 1 (mono) or 2 (stereo)!\n" );
            exit( EXIT_FAILURE );
        }
    
        ReadNumber( 4, "SampleRate" );
    
        ReadNumber( 4, "ByteRate" );
    
        ReadNumber( 2, "BlockAlign" );
    
        temp =
            ReadNumber( 2, "BitePerSample" );
    
        if ( temp != 8 && temp != 16 )
        {
            fprintf( stderr, "Error: Expected BitsPerSample to be 8 or 16!\n" );
            exit( EXIT_FAILURE );
        }
    
        VerifyLabel( "data" );
    
        ReadNumber( 4, "Subchunk2Size" );
    
        return 0;
    }
    
    void VerifyLabel( const char *label )
    {
        for ( const char *p = label; *p != '\0'; p++ )
        {
            int c;
    
            if ( (c = getchar()) == EOF )
            {
                fprintf( stderr, "input error while verifying \"%s\" label!\n", label );
                exit( EXIT_FAILURE );
            }
    
            if ( (uint_fast8_t)*p != c )
            {
                fprintf( stderr, "did not find \"%s\" label in expected position!\n", label );
                exit( EXIT_FAILURE );
            }
        }
    
        fprintf( stderr, "\"%s\" label OK\n", label );
    }
    
    uint_fast32_t ReadNumber( int num_bytes, const char *fieldname )
    {
        int c;
    
        uint_fast32_t sum = 0;
    
        if ( num_bytes < 1 || num_bytes > 4 )
        {
            fprintf( stderr, "this function only supports reading between 1 and 4 bytes\n" );
            exit( EXIT_FAILURE );
        }
    
        for ( int i = 0; i < num_bytes; i++ )
        {
            if ( (c = getchar()) == EOF )
            {
                fprintf( stderr, "input error while reading field \"%s\"!\n", fieldname );
                exit( EXIT_FAILURE );
            }
    
            sum += (uint_fast8_t)c << 8 * i;
        }
    
        //On most platforms, unsigned int is 32-bits. On those platforms, the following
        //line is equivalent to:
        //fprintf( stderr, "%s: %u\n", fieldname, sum );
        fprintf( stderr, "%s: %" PRIuFAST32 "\n", fieldname, sum );
    
        return sum;
    }
    
    

    This is a sample output of my program:

    "RIFF" label OK
    ChunkSize: 88236
    "WAVE" label OK
    "fmt " label OK
    Subchunk1Size: 16
    AudioFormat: 1
    NumChannels: 1
    SampleRate: 44100
    ByteRate: 44100
    BlockAlign: 1
    BitePerSample: 8
    "data" label OK
    Subchunk2Size: 88200