C-Sound-File-Programs / Sound File Programs GH / addecho.c
addecho.c
Raw
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>

#define HEADER_SIZE 44 // header of a .wav is 44 bytes as short

/* Adds echoes to a .wav file specified by argv[5]
 * (OR argv[3] if using the default volume_scale or delay values
 * OR argv[1] if using default volume_scale and delay values) and writes the output .wav to
 * argv[6]/argv[4]/argv[2]
 * Provide -d and then an integer argument [delay] to indicate the delay gap heard between
 * sounds in the input .wav and their corresponding echoes (if not provided, delay is
 * defaulted to 8000 samples)
 * Provide -v and then an integer argument [volume_scale] to indicate the amount by which to scale
 * down the volume of echoing copies of each sound (volume scale defaults to a scalar of 4) */
int main (int argc, char *argv[]) {
    char *input_name, *output_name; // provided input/output file names
    FILE *input_wav, *output_wav;
    short header[HEADER_SIZE]; // stores header info that is 44 bytes in an array
    int error; // stores error code return values as part of error-checking
    int delay = 8000; // default delay value
    int volume_scale = 4; // default volume_scale value
    int check; // used for command-line processing of optional arguments delay and volume_scale
    char *errptr, *errptr2; // used for strtol() conversion
    errno = 0; // use global errno for strtol() check

    /* check if the user has inputted the correct number of arguments via command-line
     * (delay or volume_scale or both can be omitted) */
    if (!(argc == 7 || argc == 3 || argc == 5)) {
        fprintf(stderr, "Correct calling arguments should be: %s -d delay -v volume_scale input_file output_file\n"
                        "[-d delay] and [-v volume_scale] are optional arguments that may be omitted\n", argv[0]);
        return 1;
    }

    /* process command-line arguments and store -d delay or
     * -v volume_scale integers when given 5 or 7 total command-line args */
    if (argc == 7 || argc == 5) {
        while ((check = getopt(argc, argv, "d:v:")) != -1) {
            switch(check) {
                case 'd':
                    // make sure delay is not a float
                    for (int i = 0; optarg[i] != '\0'; i++) {
                        if (optarg[i] == '.') {
                            fprintf(stderr, "Delay -d should be an integer. Please provide an integer value.\n");
                            return 2;
                        }
                    }
                    delay = strtol(optarg, &errptr, 10);
                    break;
                case 'v':
                    // make sure volume_scale is not a float
                    for (int i = 0; optarg[i] != '\0'; i++) {
                        if (optarg[i] == '.') {
                            fprintf(stderr, "Volume_scale -v should be an integer. Please provide an integer value.\n");
                            return 2;
                        }
                    }
                    volume_scale = strtol(optarg, &errptr2, 10);
                    break;
                default:
                    fprintf(stderr, "Correct usage should be: %s -d delay -v volume_scale input_file output_file\n"
                                    "[-d delay] and [-v volume_scale] are optional arguments that may be omitted\n", argv[0]);
                    return 1;
            }
        }
    }

    /* check if there is an underflow/overflow
     * (if so errno = ERANGE and delay/volume_scale = INT_MAX/INT_MIN)
     * check if delay and volume scale conversions could be performed
     * (if not, delay/volume_scale = 0 and errno != 0) */
    if ((errno == ERANGE && (delay == INT_MAX || delay == INT_MIN)) || (errno != 0 && delay == 0)) {
        fprintf(stderr, "strtol() conversion error. Delay -d value should be an integer\n");
        return 2;
    }
    if ((errno == ERANGE && (volume_scale == INT_MAX || volume_scale == INT_MIN)) || (errno != 0 && volume_scale == 0)) {
        fprintf(stderr, "strtol() conversion error. Volume_scale -v value should be an integer\n");
        return 2;
    }

    // check if delay and volume_scale argument integers are valid
    if (delay <= 0) {
        fprintf(stderr, "Delay -d should be an integer > 0. Otherwise, "
                        "echoes cannot be produced properly for sounds.\n");
        return 2;
    }
    if (volume_scale <= 0) {
        fprintf(stderr, "Volume_scale -v should be an integer > 0. "
                        "Otherwise, we get division by 0 errors or unclear volume scaling.\n");
        return 2;
    }

    // assign input/output file names based on whether optional arguments were provided or not
    if (argc == 3) {
        input_name = argv[1];
        output_name = argv[2];
    } else if (argc == 5) {
        input_name = argv[3];
        output_name = argv[4];
    } else {
        input_name = argv[5];
        output_name = argv[6];
    }

    // check if input/output files exist and are in proper format
    input_wav = fopen(input_name, "rb");
    if (input_wav == NULL) {
        fprintf(stderr, "Error: unable to open input file\n");
        return 3;
    }
    output_wav = fopen(output_name, "wb");
    if (output_wav == NULL) {
        fprintf(stderr, "Error: unable to open output file\n");
        return 3;
    }

    /* modify input header by setting pointers to array elements and changing them via de-referencing,
    we do this to report the new sample lengths of our output .wav file to accurately represent the
    result of mixing echo buffers with original input */
    fread(header, HEADER_SIZE, 1, input_wav);
    short *ptr_position_2 = &header[2];
    *ptr_position_2 = *ptr_position_2 + (delay * 2);
    short *ptr_position_20 = &header[20];
    *ptr_position_20 = *ptr_position_20 + (delay * 2);

    // copying modified header to output file
    error = fwrite(header, HEADER_SIZE, 1, output_wav);
    if (error != 1) {
        fprintf(stderr, "Error: unable to write the full header\n");
        return 4;
    }

    /* dynamically allocate memory for the echo buffer to hold the
     * first _delay_ volume-scaled samples of the input .wav */
    short *echo_buffer = malloc(sizeof(short) * delay);
    if (echo_buffer == NULL) {
        fprintf(stderr, "Echo buffer malloc() failed\n");
        return 5;
    }
    // populate the echo_buffer array with zeroes in preparation for the first mixing
    for (int i = 0; i < delay; i++) {
        echo_buffer[i] = 0;
    }

    // allocate memory for an array that holds the current _delay_ samples we are looking at
    short *sample_storage = malloc(sizeof(short) * delay);
    if (sample_storage == NULL) {
        fprintf(stderr, "Sample storage malloc() failed\n");
        return 5;
    }

    // stores the first _delay_ samples for processing in the sample_storage array
    int numRead = fread(sample_storage, sizeof(short), delay, input_wav);

    /* if _delay_ > total samples in input .wav, we have to wait
     * a total of _delay_ samples before producing the echo */
    int x = delay - numRead;
    if (x > 0) {
        // write all the original sound to output
        error = fwrite(sample_storage, sizeof(short), numRead, output_wav);
        if (error != numRead) {
            fprintf(stderr, "Error: could not write samples\n");
            return 6;
        }

        // add all remaining delays
        for (int i = 0; i < x; i++){
            short empty = 0;
            error = fwrite(&empty, sizeof(short), 1, output_wav);
            if (error != 1) {
                fprintf(stderr, "Error: could not write samples\n");
                return 6;
            }
        }

        // add echo scaled by volume
        for (int i = 0; i < numRead; i++) {
            short remain = sample_storage[i] / volume_scale;
            error = fwrite(&remain, sizeof(short), 1, output_wav);
            if (error != 1) {
                fprintf(stderr, "Error: could not write samples\n");
                return 6;
            }
        }
    } else {
        /* the bulk of addecho.c output writing takes place here. while we have read enough
         * samples to produce a full echo, we proceed with mixing and repopulating echo buffers */
        while (numRead == delay) {
            /* apply the mixing using the previous echo buffer (the first echo buffer is initialized to 0)
             * and write the result to the output file */
            for (int i = 0; i < delay; i++) {
                short mixing = sample_storage[i] + echo_buffer[i];
                error = fwrite(&mixing, sizeof(short), 1, output_wav);
                if (error != 1) {
                    fprintf(stderr, "Error: could not write samples\n");
                    return 6;
                }
            }

            /* clear the buffer to prepare it for the next buffer population,
             * else we might reuse old buffer values */
            for (int i = 0; i < delay; i++) {
                echo_buffer[i] = 0;
            }

            // produce the new echo buffer based on the current _delay_ samples
            for (int i = 0; i < delay; i++) {
                short volume_scaled_echo = sample_storage[i] / volume_scale;
                echo_buffer[i] = volume_scaled_echo;
            }

            /* clear the storage of _delay_ samples to prepare for next
             * sample_storage population, to prevent reuse */
            for (int i = 0; i < delay; i++) {
                sample_storage[i] = 0;
            }

            // read the next _delay_ samples for processing and store them in sample_storage
            numRead = fread(sample_storage, sizeof(short), delay, input_wav);
        }

        /* apply the mixing for the final iteration as now
         * sample_storage may not have exactly _delay_ samples */
        for (int i = 0; i < delay; i++) {
            short mixing = sample_storage[i] + echo_buffer[i];
            error = fwrite(&mixing, sizeof(short), 1, output_wav);
            if (error != 1) {
                fprintf(stderr, "Error: could not write samples\n");
                return 6;
            }
        }

        /* write the final echo buffer to output based on the remaining samples that
         * are not equal to _delay_. this avoids cutting off the very last echo prematurely
         * before it plays out in its entirety */
        for (int i = 0; i < numRead; i++) {
            short remain = sample_storage[i] / volume_scale;
            error = fwrite(&remain, sizeof(short), 1, output_wav);
            if (error != 1) {
                fprintf(stderr, "Error: could not write samples\n");
                return 6;
            }
        }
    }

    // close files after use and error-check
    error = fclose(input_wav);
    if (error != 0) {
        fprintf(stderr, "Error: fclose failed to close input file\n");
        return 7;
    }
    error = fclose(output_wav);
    if (error != 0) {
        fprintf(stderr, "Error: fclose failed to close output file\n");
        return 7;
    }

    // free dynamically allocated memory after use
    free(echo_buffer);
    free(sample_storage);

    return 0;
}