【问题标题】:WAV File Synthesis From Scratch - C从头开始合成 WAV 文件 - C
【发布时间】:2012-06-06 07:57:10
【问题描述】:

最近我在我的 CS 101 课程中看到一个视频讲座,启发我开始使用 C 语言中的 WAV 文件格式。我今天的项目一直在使用简单的数学正弦函数创建声音。尽管有一些障碍,我的程序现在可以接受多个输入(波的频率、波的幅度、采样率等)并创建一个包含指定音高的 wav 文件。

但是,在我的电脑扬声器上播放这些音调时,会发出一种奇怪的、有节奏的爆裂声,它会随着采样率的不同而变化。在较高的采样率下,爆裂声的频率会增加并变成令人讨厌的呜呜声。

奇怪的是,相同文件的不同计算机之间的爆裂声是一致的。

下面我将发布用于生成 WAV 文件的代码。任何有关可能导致这种现象的原因的见解都将受到赞赏。这可能只是我在某个地方犯的一个愚蠢的错误。 :)

#include <stdio.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <math.h>

struct WAVHeader {
    char ChunkID[4];
    uint32_t ChunkSize;
    char RIFFType[4];
};

struct FormatHeader {
    char ChunkID[4];
    uint32_t ChunkSize;
    uint16_t CompressionCode;
    uint16_t Channels;
    uint32_t SampleRate;
    uint32_t AvgBytesPerSec;
    uint16_t BlockAlign;
    uint16_t SigBitsPerSamp;
};

struct DataHeader {
    char ChunkID[4];
    uint32_t ChunkSize;

};


void main(int argc, char * argv[]) {

//Check for valid number of arguments or display help
if(argc < 8) {
    printf("Usage:\n./Tone -l [length] -s [frequency] [amplitude] -o [output-file] -r [sample-rate]\n");
    printf("-l length of tone to produce in seconds\n");    
    printf("-s Creates sine wave. Can be used multiple times. Frequency (Hz) and amplitude (0 - 32767) of each tone. \n");  
    printf("-o File to write to\n");
    printf("-r samples per second (kHz). Note: Must be double highest frequency in tone.\n");   
    return;
}

//Organize arguments
int length, sinf[10], sina[10], samplerate;
memset(sinf, 0, sizeof(int) * 10);
memset(sina, 0, sizeof(int) * 10);
char * output = NULL;
int i = 0;
int count;
for(count = 1; count < argc; count++){
    char first = *argv[count];
    int second = *(argv[count] + 1);    
    if (first == '-') {
        switch (second) {
            case 's':
                sinf[i] = atoi(argv[count+1]);
                sina[i] = atoi(argv[count+2]);
                i++;
                break;
            case 'l':
                length = atoi(argv[count+1]);
                break;
            case 'o':
                output = argv[count+1];
                break;
            case 'r':
                samplerate = atoi(argv[count+1]) * 1000;
                break;
        }
    }
}

//Allocate memory for wav file
size_t size = sizeof(struct WAVHeader) + sizeof(struct FormatHeader) + sizeof(struct DataHeader) + (length * samplerate * 2);
void * buffer = malloc(size);

//Fill buffer with headers
struct WAVHeader * WAV = (struct WAVHeader *)buffer;
struct FormatHeader * Format = (struct FormatHeader *)(WAV + 1);
struct DataHeader * Data = (struct DataHeader *)(Format + 1);

strcpy(WAV->ChunkID, "RIFF");
WAV->ChunkSize = (uint32_t)size - 8;
strcpy(WAV->RIFFType, "WAVE");

strcpy(Format->ChunkID, "fmt ");
Format->ChunkSize = 16;
Format->CompressionCode = 1;
Format->Channels = 1;
Format->SampleRate = (uint32_t)samplerate;
Format->SigBitsPerSamp = 16;
Format->BlockAlign = 2;
Format->AvgBytesPerSec = Format->BlockAlign * samplerate;

strcpy(Data->ChunkID, "data");
Data->ChunkSize = length * samplerate * 2;

//Generate Sound
printf("Generating sound...\n");
short * sound = (short *)(Data + 1);
short total;
float time;
float increment = 1.0/(float)samplerate;
for (time = 0; time < length; time += increment){
    total = 0;
    for (i = 0; i < 10; i++) {
        total += sina[i] * sin((float)sinf[i] * time * (2 * 3.1415926));
    }
    *(sound + (int)(time * samplerate)) = total;
    //printf("Time: %f Value: %hd\n", time, total);
}

//Write buffer to file
FILE * out = fopen(output, "w");
fwrite(buffer, size, 1, out);
printf("Wrote to %s\n", output);

return;

}

【问题讨论】:

  • 如果未发出-r 选项,samplerate 会发生什么情况?
  • 这不是你的问题,但你应该使用 memcpy,而不是 strcpy,因为它会复制尾随的 '\0'。
  • 不错@Michael。我很惊讶还没有把整个事情搞砸。 :)
  • 哦。它没有搞砸的原因是因为我在犯错后马上把它改写了。好吧。如果我做不到好,我还不如走运。 :D
  • 顺便说一句,我还需要 stdint.h 和 stdlib.h 来编译,(以及 gcc -lm 来链接)

标签: c math audio signal-processing pitch


【解决方案1】:

我认为这是你的核心问题:

*(sound + (int)(time * samplerate)) = total;

我怀疑由于浮点舍入误差,(time*samplerate) 并不总是在整数边界上增加。因此,由于舍入误差,一些样本位置被跳过和/或覆盖。这只是一个猜测。

而且,随着“时间”的增加,“时间 * 频率 * 2PI”的乘法将在浮点数内溢出。因此,您应该将“时间”标准化,使其不会永远增加。

无论如何,我验证了这个修改后的循环可以正常工作(和听起来):

float TWOPI = 6.28318531f;
unsigned int sample_count = length * samplerate;

for (unsigned int i = 0; i < sample_count; i++)
{
    unsigned int j = i % samplerate; // normalize the sample position so that we don't blow up in the subsequent multiplication
    float f = 0.0f;
    int result;

    for (int x = 0; x < 10; x++)
    {
        f += sina[x] * sin((sinf[x] * j * TWOPI) / samplerate);
    }

    result = (long)f;

    //clamp to 16-bit
    if (result > 32767)
    {
        result = 32767;
    }
    else if (result < -32768)
    {
        result = -32768;
    }

    sound[i] = (short)result;

    //printf("%d\n", sound[i]);

}

【讨论】:

  • 循环到 10 是为了支持最多 10 个正弦加在一起。
  • 哦,我明白了。我更新了我的答案以包括他的数组循环。并在将浮子套回短路之前正确地钳制他的结果。
  • 感谢@selbie 的建议。我试试看能不能治好我的病。
  • 非常感谢,塞尔比。一切听起来都很棒。现在我要去制作音乐了。
猜你喜欢
  • 2013-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
  • 2012-02-10
  • 1970-01-01
相关资源
最近更新 更多