Skip to content

Instantly share code, notes, and snippets.

@akoskovacs
Last active November 8, 2015 21:50
Show Gist options
  • Save akoskovacs/3690d1d91e11d6c5a5df to your computer and use it in GitHub Desktop.
Save akoskovacs/3690d1d91e11d6c5a5df to your computer and use it in GitHub Desktop.
WAV file tone writer
440 500
495 500
550 500
586 500
660 500
733 500
825 500
880 500
1320 500
990 250
1056 250
1188 250
1320 125
1188 125
1056 250
990 250
880 500
880 250
1056 250
1320 500
1188 250
1056 250
990 750
1056 250
1188 500
1320 500
1056 500
880 500
880 500
1188 500
1408 250
1760 500
1584 250
1408 250
1320 750
1056 250
1320 500
1188 250
1056 250
990 500
990 250
1056 250
1188 500
1320 500
1056 500
880 500
880 500
1320 500
990 250
1056 250
1188 250
1320 125
1188 125
1056 250
990 250
880 500
880 250
1056 250
1320 500
1188 250
1056 250
990 750
1056 250
1188 500
1320 500
1056 500
880 500
880 500
1188 500
1408 250
1760 500
1584 250
1408 250
1320 750
1056 250
1320 500
1188 250
1056 250
990 500
990 250
1056 250
1188 500
1320 500
1056 500
880 500
880 500
660 1000
528 1000
594 1000
495 1000
528 1000
440 1000
419 1000
495 1000
660 1000
528 1000
594 1000
495 1000
528 500
660 500
880 1000
838 2000
660 1000
528 1000
594 1000
495 1000
528 1000
440 1000
419 1000
495 1000
660 1000
528 1000
594 1000
495 1000
528 500
660 500
880 1000
838 2000
/*
* Simple wav file writer. The math library must be linked to
* the executable.
* Compile with: '$ cc wav.c -o wav -lm', optionally append '-ggdb'
* to enable debugging.
* The input of the executable must be a textfile with two integer
* columns. The first is the frequency, the second is the duration
* in miliseconds. The file ends with and EOF marker. Example:
* '440 1000'
* '640 500'
* Comments are not supported.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <math.h>
/* Structures which implement on-disk binary data
* have to be packed to their real sizes. No standard
* procedure exist, so this is GCC stuff. Also proper
* int types must be used to avoid misalignment.
*/
#ifndef __packed
# define __packed __attribute__((packed))
#endif // __packed
struct __packed WavHeader {
char subchunk_id[4]; /* 'fmt\x20' */
uint32_t size;
uint16_t audio_format; /* 1 for WAVE_FORMAT_PCM */
uint16_t channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
};
struct __packed RiffHeader {
int8_t riff_magic[4]; /* 'RIFF' */
uint32_t size;
int8_t format[4]; /* 'WAVE' */
};
struct __packed PcmHeader {
uint16_t bits_per_sample; /* 8 by default */
};
struct Tone {
int freq; /* in Hertz */
int time ; /* in miliseconds */
uint8_t *samples; /* array of the samples */
int sample_size; /* size of the 'samples' array */
//int repeat; /* sample repetition count */
struct Tone *next;
};
struct WavFile {
FILE *fp;
const char *file_name;
struct RiffHeader *r_hdr;
struct WavHeader *w_hdr;
struct PcmHeader *pcm_hdr;
struct Tone *first_tone;
struct Tone *last_tone;
};
struct RiffHeader *new_riff_header(void)
{
struct RiffHeader *hdr = malloc(sizeof(struct RiffHeader));
if (hdr == NULL) {
return NULL;
}
/* No null terminator in these strings */
memcpy(hdr->riff_magic, "RIFF", 4);
memcpy(hdr->format, "WAVE", 4);
hdr->size = 0x2b; // size of this header
return hdr;
}
struct PcmHeader *new_pcm_header(void)
{
struct PcmHeader *pcm = malloc(sizeof(struct PcmHeader));
if (pcm == NULL)
return NULL;
pcm->bits_per_sample = 8;
return pcm;
}
struct WavHeader *new_wav_header(void)
{
struct WavHeader *hdr = malloc(sizeof(struct WavHeader));
if (hdr == NULL) {
return NULL;
}
memcpy(hdr->subchunk_id, "fmt\x20", 4);
/* Fill the header with default values for PCM */
hdr->audio_format = 1; /* WAVE_FORMAT_PCM */
hdr->channels = 1;
hdr->sample_rate = 16000;
hdr->byte_rate = 8000;
hdr->block_align = 1;
hdr->size = 0x10;
return hdr;
}
struct WavFile *new_wav_file(const char *name)
{
struct WavFile *wf;
FILE *fp = fopen(name, "w");
if (fp == NULL) {
return NULL;
}
wf = malloc(sizeof(struct WavFile));
if (wf == NULL) {
fclose(fp);
return NULL;
}
wf->fp = fp;
wf->r_hdr = new_riff_header();
wf->w_hdr = new_wav_header();
wf->pcm_hdr = new_pcm_header();
wf->first_tone = NULL;
wf->last_tone = NULL;
return wf;
}
struct Tone *new_tone(void)
{
struct Tone *t = malloc(sizeof(struct Tone));
t->freq = t->time = 0;
t->samples = NULL;
t->sample_size = 0;
t->next = NULL;
return t;
}
struct Tone *add_tone(struct WavFile *wf, struct Tone *tone)
{
/* This is just a standard linked list, nothing to see here */
/* Just scroll down a little */
if (wf == NULL || tone == NULL) {
return NULL;
}
if (wf->first_tone == NULL) {
wf->first_tone = tone;
} else {
if (wf->last_tone == NULL) {
return NULL;
}
wf->last_tone->next = tone;
}
wf->last_tone = tone;
return tone;
}
struct Tone *new_sine_tone(struct WavFile *wf, int frequency, int duration)
{
int i;
struct Tone *t;
double rate;
if (wf == NULL || wf->w_hdr == NULL) {
return NULL;
}
t = new_tone();
t->freq = frequency;
t->time = duration;
/* This array holds each quantized wave point */
/* The sample size is proportionate to the frequency */
t->sample_size = (wf->w_hdr->sample_rate*2)/frequency;
t->samples = calloc(sizeof(uint8_t), t->sample_size);
if (t->samples == NULL) {
free(t);
return NULL;
}
/* No need for samples when the frequency or duration is zero */
if (frequency != 0 && duration != 0) {
/* Calculate the sampling point for a half sine wave */
rate = M_PI/(t->sample_size/2);
for (i = 0; i < t->sample_size; i++) {
/* Quantize the sine wave to fit inside a uint8_t */
t->samples[i] = (uint8_t)(sin(i*rate)*127)+127;
}
} else { /* Hello silence my old friend... */
for (i = 0; i < t->sample_size; i++) {
t->samples[i] = 128;
}
}
return t;
}
struct Tone *add_sine_tone(struct WavFile *wf, int frequency, int duration)
{
struct Tone *t = new_sine_tone(wf, frequency, duration);
if (t == NULL) {
return NULL;
}
add_tone(wf, t);
return t;
}
int write_wav_file(struct WavFile *wf)
{
struct Tone *t;
double sample_time;
off_t sample_pos;
int repeat;
uint32_t sample_size = 0;
if (wf == NULL || wf->fp == NULL || wf->w_hdr == NULL
|| wf->r_hdr == NULL || wf->pcm_hdr == NULL) {
return 1;
}
fwrite(wf->r_hdr, sizeof(struct RiffHeader), 1, wf->fp);
fwrite(wf->w_hdr, sizeof(struct WavHeader), 1, wf->fp);
fwrite(wf->pcm_hdr, sizeof(struct PcmHeader), 1, wf->fp);
fwrite("data", 4, 1, wf->fp);
sample_pos = ftell(wf->fp);
/* we have to get back here later... */
fseek(wf->fp, 4, SEEK_CUR); // don't need to know the size yet
for (t = wf->first_tone; t != NULL; t = t->next) {
if (t->samples == NULL) {
continue;
}
/* The full waves have to be aligned to the tone duration */
sample_time = t->time/((1.0/wf->w_hdr->sample_rate)*t->sample_size*2E3);
for (repeat = 0; repeat < (int)sample_time; repeat++) {
fwrite(t->samples, sizeof(char), t->sample_size, wf->fp);
sample_size += t->sample_size;
}
}
/* Get back to sample_size's offset, and make an update */
fseek(wf->fp, sample_pos, SEEK_SET);
fwrite(&sample_size, sizeof(uint32_t), 1, wf->fp);
return 0;
}
void free_wav_file(struct WavFile *wf)
{
struct Tone *t, *nt;
if (wf == NULL) {
return;
}
free(wf->r_hdr);
free(wf->w_hdr);
free(wf->pcm_hdr);
for (t = wf->first_tone; t != NULL; t = nt) {
nt = t->next;
free(t->samples);
free(t);
}
if (wf->fp != NULL) {
fclose(wf->fp);
}
free(wf);
}
int main(int argc, const char *argv[])
{
struct WavFile *wf;
FILE *tf;
if (argc <= 2) {
fprintf(stderr, "Usage: %s TONE_FILE OUTPUT_FILE\n", argv[0]);
fprintf(stderr, "The TONE_FILE must contain a list of frequencies and durations.\n");
return 1;
}
wf = new_wav_file(argv[2]);
if (wf == NULL) {
fprintf(stderr, "Cannot create wav file. :(\n");
return 1;
}
tf = fopen(argv[1], "r");
if (tf == NULL) {
fprintf(stderr, "Cannot open tone file. :(\n");
return 1;
}
while (!feof(tf)) {
int f, d; /* frequency and duration */
/* No error handling here, because of reasons */
/* Nothing could cause proplems, right? */
fscanf(tf, "%d %d\n", &f, &d);
add_sine_tone(wf, f, d);
}
write_wav_file(wf);
fclose(tf);
free_wav_file(wf);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment