Gas station without pumps

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;
}

11 Comments »

  1. If channel numbers is 2, things do not work as expected. And buffer should be a short int, probably even for num_channels == 1;

    I’d say this line

    write_little_endian(bytes_per_sample* num_samples*num_channels, 4, wav_file);

    should be

    write_little_endian(bytes_per_sample* num_samples, 4, wav_file);

    Once i’ve made these changes (and num_channels = 2), wavinfo was happy with the result and i was able to hear noise on both ears.

    Anyway, thank you for a great program :)

    Comment by cpanceac — 2013 January 17 @ 05:30 | Reply

    • Thanks for the bug report. When I get some spare time, I might revisit this code. I was originally planning a whole series of blog posts on using an Arduino for sound synthesis, but I got sidetracked by other projects and never completed it. I may come back to that project next summer.

      Comment by gasstationwithoutpumps — 2013 January 17 @ 09:49 | Reply

  2. You shoud use
    fopen(filename, “wb”);
    instead of
    fopen(filename, “w”);

    This will avoid adding new line and carridge returns in “random” places on windows, as the file is opend as a binary.

    Comment by Søren — 2013 June 19 @ 07:42 | Reply

    • Probably true—I don’t use Windows and am not familiar with how it messes up file I/O. Since the files are binary, opening them “wb” instead of “w” shouldn’t hurt and may help.

      Comment by gasstationwithoutpumps — 2013 June 19 @ 09:35 | Reply

  3. […] Making WAV files from C programs […]

    Pingback by 2013 in review | Gas station without pumps — 2013 December 31 @ 11:19 | Reply

  4. Helpful post. This was just the thing, that is, to have a very simple interface to perform a very simple task. Sometimes these audio file standards have so many features that the bulk of the applications don’t use, and the supporting libraries get large and more difficult to learn.

    In some applications it’s better to go to the basics and implement only what is needed for the task.

    A minor technicality:
    “int main(int argc, char * argv)”

    For accuracy, this should be
    int main(int argc, char * argv[])
    .
    .
    .
    or if you prefer,
    int main(int argc, char ** argv)
    .
    .
    .

    Makes no difference in your sample program because you aren’t using input arguments, but it would become apparent something is wrong if somebody quickly modified the code to try to take the output filename from the input args.

    Comment by RJB — 2014 June 19 @ 14:51 | Reply

  5. […] In toying with the idea of going back to doing research on digital music synthesis, which I had not done much in since about 1984, I posted on my blog about how to create minimal WAV-format files in C, which has since become one of my most searched-for posts, as the simple code is good for incorporating into programming courses using digital media as a theme: https://gasstationwithoutpumps.wordpress.com/2011/10/08/making-wav-files-from-c-programs/ […]

    Pingback by Sabbatical leave report | Gas station without pumps — 2015 September 8 @ 22:23 | Reply

  6. You have done great job.
    But i have one question for you.
    I also have project where i utilize you code but now i want to write double/float data through that buffer variable to fstream.
    For that what should i do?

    Comment by spatels — 2016 May 19 @ 02:17 | Reply

    • write_little_endian((unsigned int)(data[i]),bytes_per_sample, wav_file);

      Means in write_little_endian function instead of unsigned int , I have double type data for writing because i have for specific musical application.

      Comment by spatels — 2016 May 19 @ 02:22 | Reply

      • I believe that wav files use fixed-point, not floating-point representation. You will have to scale your numbers and convert them to unsigned ints. Note that unsigned ints are always positive, so the center value is not as zero but midway through the range.

        Comment by gasstationwithoutpumps — 2016 May 19 @ 07:44 | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: