Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@cellularmitosis
Last active January 21, 2022 00:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cellularmitosis/f6575cf9f397bfd5edf5bf17dc605f4c to your computer and use it in GitHub Desktop.
Save cellularmitosis/f6575cf9f397bfd5edf5bf17dc605f4c to your computer and use it in GitHub Desktop.
Basic audio filters in C

Blog 2022/1/16

<- previous | index | next ->

Basic audio filters in C

Here's an implementation of a few basic audio filters:

  • a "copy" filter (simply copies the bytes)
  • a "quiet" filter which reduces the volume by 18 db
  • an "echo" filter which creates a single echo, reduced by 10db

Use the make to run the demos:

  • make copy
  • make quiet
  • make echo

Note: you'll need to have sox installed. On macOS, brew install sox. Also, brew install wget.

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
// The trivial filter: simply copy the input samples.
// 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 sample_t;
void process(sample_t* input_buff, size_t num_samples, sample_t* output_buff) {
for (size_t i = 0; i < num_samples; i += 1) {
output_buff[i] = input_buff[i];
}
}
int main(int argc, char** argv) {
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;
}
size_t sample_size = sizeof(sample_t);
const size_t samples_per_buffer = 32;
sample_t input_buff[samples_per_buffer] = {0};
sample_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);
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;
}
}
fclose(input_fd);
fclose(output_fd);
return EXIT_SUCCESS;
}
#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;
}
copy: copy-filter input-10s.raw
./copy-filter input-10s.raw output.raw
sox -r 22050 -b 16 -e signed-integer --endian little -c 1 output.raw output.wav
play output.wav
quiet: quiet-filter input-10s.raw
./quiet-filter input-10s.raw output.raw
sox -r 22050 -b 16 -e signed-integer --endian little -c 1 output.raw output.wav
play output.wav
echo: echo-filter input-10s.raw
./echo-filter input-10s.raw output.raw
sox -r 22050 -b 16 -e signed-integer --endian little -c 1 output.raw output.wav
play output.wav
copy-filter: copy-filter.c
gcc -std=c99 -O0 -Wall -Werror -o copy-filter copy-filter.c
quiet-filter: quiet-filter.c
gcc -std=c99 -O0 -Wall -Werror -o quiet-filter quiet-filter.c
echo-filter: echo-filter.c
gcc -std=c99 -O0 -Wall -Werror -o echo-filter echo-filter.c
input-60s.wav:
wget -O input-60s.wav https://www2.cs.uic.edu/~i101/SoundFiles/ImperialMarch60.wav
input-10s.raw: input-60s.wav
sox input-60s.wav -t raw --endian little input-10s.raw trim 0:00 0:10 channels 1
clean:
rm -f copy-filter quiet-filter input-10s.raw output.raw output.wav
.PHONY: clean copy quiet
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
// Basic gain filter: apply -18db gain to the samples.
// 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;
}
void process(i16sample_t* input_buff, size_t num_samples, i16sample_t* output_buff) {
factor_t factor = db_gain_factor(-18);
for (size_t i = 0; i < num_samples; i += 1) {
fsample_t fsample = to_fsample(input_buff[i]);
fsample = fsample * factor;
output_buff[i] = to_i16sample(fsample);
}
}
int main(int argc, char** argv) {
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;
}
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);
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;
}
}
fclose(input_fd);
fclose(output_fd);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment