generating an .au file in c

2

Out of morbid curiosity I've been trying to come up with a program that will generate a 4 second 440 A note in C. However, playing the outputted file in VLC does not produce any music.

Using Wikipedia as a guide to the .au header on this page: http://en.wikipedia.org/wiki/Au_file_format I came up with this:

#include <stdio.h>
#include <math.h>

#define MAGIC_NUM 0x2e736e64
#define DEFAULT_OFFSET 24
#define UNKNOWN_SIZE 0xffffffff
#define BIT_32_PCM 5
#define STEREO 2
#define SAMPLE_RATE 8000
#define DURATION 4
#define MIDDLE_A 440

int main(int argc, char** argv) {
    FILE* sound;

    sound = fopen("output.au", "w");

    //write header
    fputc(MAGIC_NUM, sound);
    fputc(DEFAULT_OFFSET, sound);
    fputc(UNKNOWN_SIZE, sound);
    fputc(BIT_32_PCM, sound);
    fputc(SAMPLE_RATE, sound);
    fputc(STEREO, sound);

    //write a duration of a constant note
    int i;
    for(i = 0; i <= DURATION * SAMPLE_RATE; i++) {
        fputc((int)floor(MIDDLE_A * sin(i)), sound);
    }

    fclose(sound);

    return 0;
}

Does anyone know what I'm doing wrong?

c
audio
asked on Stack Overflow Mar 30, 2011 by FunTimesWithCode • edited Jan 11, 2019 by TylerH

4 Answers

4

You don't want the file opened as text - it may do funny things with any written linefeeds.

sound = fopen("output.au", "wb");

You don't want to write characters - use fwrite not fputc

fwrite(".snd", 1, 4, sound);

Also note that .au files are big-endian - if you're on x86 or x86_64 your native byte order is little endian, and you'll need to convert your data before writing it.

answered on Stack Overflow Mar 30, 2011 by Erik
1

The problem is fputc, it only outputs a char. You can't for example write MAGIC_NUM at once using fputc. A solution could be defining your own fput functions:

// write a word (2 bytes)
void fputw (unsigned int value, FILE* f)
{
    fputc(value & 0xff, f);
    fputc(value >> 8 & 0xff, f);
}

// write a dword (4 bytes)
void fputdw(unsigned int value, FILE* f)
{
    fputc(value & 0xff, f);
    fputc(value >> 8  & 0xff, f);
    fputc(value >> 16 & 0xff, f);
    fputc(value >> 24 & 0xff, f);
}

//write header
fputdw(MAGIC_NUM, sound);
fputdw(DEFAULT_OFFSET, sound);
fputdw(UNKNOWN_SIZE, sound);
fputdw(BIT_32_PCM, sound);
fputdw(SAMPLE_RATE, sound);
fputdw(STEREO, sound);
answered on Stack Overflow Mar 30, 2011 by Javi R
0

The comments about writing a character at a time are on point as far as that goes, but there are more fundamental problems here --

  1. The output of the sin() function is a float that ranges between -1.0 .. +1.0; you then take the floor of that, which will always be zero.

  2. The per-sample output of that sine generator needs to be converted from a float to a 32-bit integer.

  3. You need to convert sample position differently -- you need to calculate the angular frequency (in radians) that the sine wave advances along between samples.

Pseudocode like (not tested...)

SAMPLE_RATE = 44100.0
FREQ = 440.0
PI = 3.14159
DURATION = 4
AMPLITUDE = 1.0

ANGULAR_FREQ = (2 * PI * FREQ) / SAMPLE_RATE

for (int i = 0; i < (int) DURATION * SAMPLE_RATE; ++i)
{
    // get sample value in range -1.0 .. + 1.0
    floatSample = AMPLITUDE * sin(i * ANGULAR_FREQ)
    // convert to correctly scaled integer representation
    intSample = (int) floor(0x7FFFFFFF * floatSample);
    // write to file, send out DAC, whatever...
}
answered on Stack Overflow Mar 30, 2011 by bgporter
0

This is a working and tested version for C++.

createTone() builds the header and generates the tone data. This wikipedia article describes the values of the header variables DATA_OFFSET, DATA_SIZE, ENCODING, SAMPLE_RATE and CHANNELS.

NOTE: beware that .au files are generate using BIG ENDIAN, so when compiling on Intel x86/x64 you will need to make the convertion, that's why htonl is loaded.

This code uses linear PCM 8 modulation, for other modulation you will need to modify/replace pcm8() function.

#include <iostream>
#include <fstream>
#include <sstream>
#include <math.h>
#include <arpa/inet.h>

const int MAGIC_WORD = htonl(0x2e736e64);
const int DATA_OFFSET = htonl(24);
const int DATA_SIZE = htonl(0xffffffff);
const int ENCODING = htonl(0x00000002);
const int SAMPLE_RATE = 0x00001F40;
const int CHANNELS = htonl(0x00000005);
const double PI = 3.14159265358979323846L;

const int PCM_MAX = 0xffffffff;

int pcm8(double value) {
    double v = ((value + 1) / 2);

    double delta = 1.0 / pow(2, 8);
    double ret = round(v / delta);
    int r = (int) ret;

    return r;
}

void createTone(int duration, double frec) {
    std::ofstream file;
    file.open("tone.au", std::ios::binary);

    // header
    const int sampleRate = htonl(SAMPLE_RATE);
    file.write((char*) &MAGIC_WORD, sizeof(int));
    file.write((char*) &DATA_OFFSET, sizeof(int));
    file.write((char*) &DATA_SIZE, sizeof(int));
    file.write((char*) &ENCODING, sizeof(int));
    file.write((char*) &sampleRate, sizeof(int));
    file.write((char*) &CHANNELS, sizeof(int));

    // data
    double t = 0;

    while (t < duration) {
        int level = htonl(pcm8(sin(2 * PI * frec * t)));
        file.write((char*) &level, sizeof(int));
        t += 1.0 / ((double) SAMPLE_RATE);
    }

    file.close();
}

int main(int argc, char** argv) {
    createTone(4, 440.0);
    return 0;
}
answered on Stack Overflow Sep 30, 2014 by jabaldonedo

User contributions licensed under CC BY-SA 3.0