Last active
November 8, 2015 21:50
-
-
Save akoskovacs/3690d1d91e11d6c5a5df to your computer and use it in GitHub Desktop.
WAV file tone writer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
440 500 | |
495 500 | |
550 500 | |
586 500 | |
660 500 | |
733 500 | |
825 500 | |
880 500 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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