generating an .au file in c
Out of morbid curiosity I've been trying to come up with a program that will generate开发者_C百科 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?
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.
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);
The comments about writing a character at a time are on point as far as that goes, but there are more fundamental problems here --
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.The per-sample output of that sine generator needs to be converted from a float to a 32-bit integer.
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...
}
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;
}
精彩评论