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