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

19 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

    • According to the standard the mode parameter in fopen does not have a “b”. So this is Windows special – messing with the data.

      Comment by Arjan — 2019 June 7 @ 08:53 | Reply

      • Because I almost never use Windows, and the code was developed on a Mac, I find your claim that this is “Windows special” rather dubious.
        According to http://www.cplusplus.com/reference/cstdio/fopen/

        With the mode specifiers above the file is open as a text file. In order to open a file as a binary file, a “b” character has to be included in the mode string. This additional “b” character can either be appended at the end of the string (thus making the following compound modes: “rb”, “wb”, “ab”, “r+b”, “w+b”, “a+b”) or be inserted between the letter and the “+” sign for the mixed modes (“rb+”, “wb+”, “ab+”).

        Comment by gasstationwithoutpumps — 2019 June 7 @ 10:07 | 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

        • Hi,
          How can this be done? A small example code would help

          Comment by Sameena Shaikh — 2019 July 1 @ 03:38 | Reply

          • The last example in in this post already converts floating-point sine wave computations into fixed-point integers, though it looks like I assumed that the values were signed (not unsigned as my comment of 2016 May 19 says) in both the WAV file and the internal format. I has almost 8 years since I wrote this code, so I’d have to look up the WAV format again to see which is correct (more likely the original code, because I did look up the WAV format then).

            Comment by gasstationwithoutpumps — 2019 July 1 @ 07:45 | Reply

  7. hi,i had generated a wav file.but it is not audible…pls someone help me.

    Comment by sat — 2016 November 8 @ 23:07 | Reply

    • There is not enough information in your comment to help you. If you are taking a course, I suggest consulting with the instructor or teaching assistant. If not, try to find someone locally to help you, as it seems that you don’t know enough about asking questions on the internet to benefit much from remote assistance.

      Comment by gasstationwithoutpumps — 2016 November 9 @ 08:05 | Reply

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

    Pingback by Spike in views on Monday | Gas station without pumps — 2017 September 22 @ 12:06 | Reply

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

    Pingback by Blog stats for 2017 | Gas station without pumps — 2018 January 1 @ 11:47 | Reply


RSS feed for comments on this post. TrackBack URI

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.