|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <stdint.h> |
|
#include <math.h> |
|
#include <string.h> |
|
#include <assert.h> |
|
|
|
// Echo filter: the input repeated at lower gain after a delay. |
|
|
|
// The first argument is the path to a headerless raw int16_t PCM file. |
|
// The output is sent to stdout. |
|
|
|
void usage(FILE* stream, char* program_name) { |
|
fprintf(stream, "Usage: %s input.raw output.raw\n", program_name); |
|
} |
|
|
|
typedef int16_t i16sample_t; |
|
typedef float fsample_t; |
|
|
|
fsample_t to_fsample(i16sample_t sample) { |
|
return ((fsample_t)sample) / INT32_MAX; |
|
} |
|
|
|
i16sample_t to_i16sample(fsample_t sample) { |
|
return (i16sample_t)(sample * INT32_MAX); |
|
} |
|
|
|
typedef float db_t; |
|
typedef float factor_t; |
|
|
|
factor_t db_gain_factor(db_t gain) { |
|
if (gain < 0) { |
|
return 1.0 / db_gain_factor(-1.0 * gain); |
|
} |
|
// Thanks to https://sound.stackexchange.com/a/38725 |
|
factor_t factor = pow(10.0, gain / 20.0); |
|
return factor; |
|
} |
|
|
|
struct _ring_t { |
|
i16sample_t* samples; |
|
size_t head_index; |
|
size_t tail_index; |
|
size_t capacity; |
|
size_t len; |
|
}; |
|
typedef struct _ring_t ring_t; |
|
|
|
void init_ring(ring_t* ring, size_t capacity) { |
|
size_t num_bytes = sizeof(i16sample_t) * capacity; |
|
i16sample_t* samples = (i16sample_t*)malloc(num_bytes); |
|
if (samples == NULL) { |
|
perror("Malloc failed"); |
|
exit(1); |
|
} |
|
ring->samples = samples; |
|
ring->head_index = capacity - 1; |
|
ring->tail_index = capacity - 1; |
|
ring->capacity = capacity; |
|
ring->len = 0; |
|
} |
|
|
|
void init_ring_full(ring_t* ring, size_t capacity) { |
|
init_ring(ring, capacity); |
|
size_t num_bytes = sizeof(i16sample_t) * ring->capacity; |
|
memset(ring->samples, 0, num_bytes); |
|
ring->head_index = 0; |
|
ring->len = capacity; |
|
} |
|
|
|
void deinit_ring(ring_t* ring) { |
|
free(ring->samples); |
|
ring->samples = NULL; |
|
ring->head_index = 0; |
|
ring->tail_index = 0; |
|
ring->capacity = 0; |
|
ring->len = 0; |
|
} |
|
|
|
void ring_push(ring_t* ring, i16sample_t sample) { |
|
if (ring->len > 0) { |
|
if (ring->head_index == 0) { |
|
ring->head_index = ring->capacity - 1; |
|
} else { |
|
ring->head_index -= 1; |
|
} |
|
} |
|
ring->samples[ring->head_index] = sample; |
|
if (ring->len == ring->capacity) { |
|
if (ring->tail_index == 0) { |
|
ring->tail_index = ring->capacity - 1; |
|
} else { |
|
ring->tail_index -= 1; |
|
} |
|
} else { |
|
ring->len += 1; |
|
} |
|
} |
|
|
|
i16sample_t ring_get(ring_t* ring, size_t index) { |
|
assert(index < ring->len); |
|
size_t internal_index; |
|
if (index <= (ring->capacity - (ring->head_index+1))) { |
|
internal_index = ring->head_index + index; |
|
} else { |
|
internal_index = index - (ring->capacity - ring->head_index); |
|
} |
|
return ring->samples[internal_index]; |
|
} |
|
|
|
i16sample_t ring_get_last(ring_t* ring) { |
|
size_t index = ring->len - 1; |
|
return ring_get(ring, index); |
|
} |
|
|
|
factor_t n3db; |
|
factor_t n10db; |
|
|
|
void process(i16sample_t* input_buff, size_t num_samples, i16sample_t* output_buff, ring_t* ring) { |
|
for (size_t i = 0; i < num_samples; i += 1) { |
|
i16sample_t isample = input_buff[i]; |
|
fsample_t fsample = to_fsample(isample); |
|
ring_push(ring, isample); |
|
fsample_t echoed_sample = to_fsample(ring_get_last(ring)); |
|
fsample = (fsample * n3db) + (echoed_sample * n10db); |
|
output_buff[i] = to_i16sample(fsample); |
|
} |
|
} |
|
|
|
int main(int argc, char** argv) { |
|
n3db = db_gain_factor(-3); |
|
n10db = db_gain_factor(-10); |
|
|
|
if (argc != 3) { |
|
usage(stderr, argv[0]); |
|
return EXIT_FAILURE; |
|
} |
|
char* input_filename = argv[1]; |
|
char* output_filename = argv[2]; |
|
|
|
FILE* input_fd = fopen(input_filename, "r"); |
|
if (input_fd == NULL) { |
|
fprintf(stderr, "Error opening %s: ", input_filename); |
|
perror(""); |
|
return EXIT_FAILURE; |
|
} |
|
|
|
FILE* output_fd = fopen(output_filename, "w"); |
|
if (output_fd == NULL) { |
|
fprintf(stderr, "Error opening %s: ", output_filename); |
|
perror(""); |
|
return EXIT_FAILURE; |
|
} |
|
|
|
ring_t ring; |
|
size_t ring_capacity = 22050; |
|
init_ring_full(&ring, ring_capacity); |
|
|
|
size_t sample_size = sizeof(i16sample_t); |
|
|
|
const size_t samples_per_buffer = 32; |
|
i16sample_t input_buff[samples_per_buffer] = {0}; |
|
i16sample_t output_buff[samples_per_buffer] = {0}; |
|
|
|
while (!feof(input_fd)) { |
|
size_t num_samples_read = fread(&input_buff, sample_size, samples_per_buffer, input_fd); |
|
if (ferror(input_fd)) { |
|
fprintf(stderr, "Error reading %s: ", input_filename); |
|
perror(""); |
|
return EXIT_FAILURE; |
|
} |
|
|
|
process(input_buff, num_samples_read, output_buff, &ring); |
|
|
|
size_t num_samples_written = fwrite(output_buff, sample_size, num_samples_read, output_fd); |
|
if (num_samples_written != num_samples_read || ferror(output_fd)) { |
|
fprintf(stderr, "Error writing %s: ", output_filename); |
|
perror(""); |
|
return EXIT_FAILURE; |
|
} |
|
} |
|
|
|
deinit_ring(&ring); |
|
fclose(input_fd); |
|
fclose(output_fd); |
|
return EXIT_SUCCESS; |
|
} |