karplus4arduino

2011 October 8

Making WAV files from C programs

Filed under: Digital music — gasstationwithoutpumps @ 21:03
Tags: ,

I’m going to try embedding some short pieces of code in this post, and later work out a better way of distributing larger blocks of code.

The code I want to share today consists of a small C file and the associated header for creating WAV files for producing sounds on a laptop or desktop computer. This is not a full-featured WAV file module: the format has a lot of options and I didn’t want the complexity of dealing with all of them. I just wanted to output monophonic sound with a reasonable sampling rate.

/* make_wav.h
 * Fri Jun 18 17:06:02 PDT 2010 Kevin Karplus
 */

#ifndef MAKE_WAV_H
#define MAKE_WAV_H

void write_wav(char * filename, unsigned long num_samples, short int * data, int s_rate);
	/* open a file named filename, write signed 16-bit values as a
		monoaural WAV file at the specified sampling rate
		and close the file
	*/

#endif
/* make_wav.c
 * Creates a WAV file from an array of ints.
 * Output is monophonic, signed 16-bit samples
 * copyright
 * Fri Jun 18 16:36:23 PDT 2010 Kevin Karplus
 * Creative Commons license Attribution-NonCommercial
 *	http://creativecommons.org/licenses/by-nc/3.0/
 */

#include <stdio.h>
#include <assert.h>

#include "make_wav.h"

void write_little_endian(unsigned int word, int num_bytes, FILE *wav_file)
{
    unsigned buf;
    while(num_bytes>0)
    {   buf = word & 0xff;
    	fwrite(&buf, 1,1, wav_file);
        num_bytes--;
	word >>= 8;
    }
}

/* information about the WAV file format from
	http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
 */

void write_wav(char * filename, unsigned long num_samples, short int * data, int s_rate)
{
    FILE* wav_file;
    unsigned int sample_rate;
    unsigned int num_channels;
    unsigned int bytes_per_sample;
    unsigned int byte_rate;
    unsigned long i;	/* counter for samples */

    num_channels = 1;	/* monoaural */
    bytes_per_sample = 2;

    if (s_rate<=0) sample_rate = 44100;
    else sample_rate = (unsigned int) s_rate;

    byte_rate = sample_rate*num_channels*bytes_per_sample;

    wav_file = fopen(filename, "w");
    assert(wav_file);	/* make sure it opened */

    /* write RIFF header */
    fwrite("RIFF", 1, 4, wav_file);
    write_little_endian(36 + bytes_per_sample* num_samples*num_channels, 4, wav_file);
    fwrite("WAVE", 1, 4, wav_file);

    /* write fmt  subchunk */
    fwrite("fmt ", 1, 4, wav_file);
    write_little_endian(16, 4, wav_file);	/* SubChunk1Size is 16 */
    write_little_endian(1, 2, wav_file);	/* PCM is format 1 */
    write_little_endian(num_channels, 2, wav_file);
    write_little_endian(sample_rate, 4, wav_file);
    write_little_endian(byte_rate, 4, wav_file);
    write_little_endian(num_channels*bytes_per_sample, 2, wav_file);  /* block align */
    write_little_endian(8*bytes_per_sample, 2, wav_file);  /* bits/sample */

    /* write data subchunk */
    fwrite("data", 1, 4, wav_file);
    write_little_endian(bytes_per_sample* num_samples*num_channels, 4, wav_file);
    for (i=0; i< num_samples; i++)
    { 	write_little_endian((unsigned int)(data[i]),bytes_per_sample, wav_file);
    }

    fclose(wav_file);
}

Note that the above code is not a full program, just a tiny library routine that can be used to generate WAV files. Here is a test program to generate sine waves (inefficiently) to see if the program works. Once you have the basic idea, you can write arbitrary programs to generate WAV files and play them back with QuickTime Player, RealPlayer, Audacity, or any of several other sound-playing programs.  This is really old-school computer music, from the days when computers were so slow that quality sounds could not be generated in real time.  It provides a good way to get started in learning the algorithms, though, as you don’t need to worry about efficiency or all the details needed to compute samples and provide them to a DAC at exactly the right rate.

/* test_make_wav.c
 * Fri Jun 18 17:13:19 PDT 2010 Kevin Karplus
 * Test program for the write_wav function in make_wav.c
 */

#include <math.h>
#include "make_wav.h"

#define S_RATE	(44100)
#define BUF_SIZE (S_RATE*2)	/* 2 second buffer */

int buffer[BUF_SIZE];

int main(int argc, char * argv)
{
    int i;
    float t;
    float amplitude = 32000;
    float freq_Hz = 440;
    float phase=0;

    float freq_radians_per_sample = freq_Hz*2*M_PI/S_RATE;

    /* fill buffer with a sine wave */
    for (i=0; i<BUF_SIZE; i++)
    {
        phase += freq_radians_per_sample;
	buffer[i] = (int)(amplitude * sin(phase));
    }

    write_wav("test.wav", BUF_SIZE, buffer, S_RATE);

    return 0;
}
Advertisements

Blog at WordPress.com.