三年前,我编写了这个简单的C程序,用于将计算机生成的音乐写入WAV文件。我对其进行了一些重构,但是对我来说看起来并不好。有什么建议吗?
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
/******************************
* Magic file format strings. *
******************************/
const char fChunkID[] = {'R', 'I', 'F', 'F'};
const char fFormat[] = {'W', 'A', 'V', 'E'};
const char fSubchunk1ID[] = {'f', 'm', 't', ' '};
const char fSubchunk2ID[] = {'d', 'a', 't', 'a'};
/********************************
* WriteWavePCM() configuration: *
* - 2 channels, *
* - frequency 44100 Hz. *
********************************/
const unsigned short N_CHANNELS = 2;
const unsigned int SAMPLE_RATE = 44100;
const unsigned short BITS_PER_BYTE = 8;
bool WriteWavePCM(short* sound, size_t pairAmount, char* fileName){
const static unsigned int fSubchunk1Size = 16;
const static unsigned short fAudioFormat = 1;
const static unsigned short fBitsPerSample = 16;
unsigned int fByteRate = SAMPLE_RATE * N_CHANNELS *
fBitsPerSample / BITS_PER_BYTE;
unsigned short fBlockAlign = N_CHANNELS * fBitsPerSample / BITS_PER_BYTE;
unsigned int fSubchunk2Size;
unsigned int fChunkSize;
FILE* fout;
size_t ws;
if (!sound || !fileName || !(fout = fopen( fileName, "w" ))) return false;
fSubchunk2Size = pairAmount * N_CHANNELS * fBitsPerSample / BITS_PER_BYTE;
fChunkSize = 36 + fSubchunk2Size;
// Writing the RIFF header:
fwrite(&fChunkID, 1, sizeof(fChunkID), fout);
fwrite(&fChunkSize, sizeof(fChunkSize), 1, fout);
fwrite(&fFormat, 1, sizeof(fFormat), fout);
// "fmt" chunk:
fwrite(&fSubchunk1ID, 1, sizeof(fSubchunk1ID), fout);
fwrite(&fSubchunk1Size, sizeof(fSubchunk1Size), 1, fout);
fwrite(&fAudioFormat, sizeof(fAudioFormat), 1, fout);
fwrite(&N_CHANNELS, sizeof(N_CHANNELS), 1, fout);
fwrite(&SAMPLE_RATE, sizeof(SAMPLE_RATE), 1, fout);
fwrite(&fByteRate, sizeof(fByteRate), 1, fout);
fwrite(&fBlockAlign, sizeof(fBlockAlign), 1, fout);
fwrite(&fBitsPerSample, sizeof(fBitsPerSample), 1, fout);
/* "data" chunk: */
fwrite(&fSubchunk2ID, 1, sizeof(fSubchunk2ID), fout);
fwrite(&fSubchunk2Size, sizeof(fSubchunk2Size), 1, fout);
/* sound data: */
ws = fwrite(sound, sizeof(short), pairAmount * N_CHANNELS, fout);
fclose(fout);
return true;
}
/************************************
* Around 23 seconds of pure techno! *
************************************/
const unsigned int N_SAMPLE_PAIRS = 1048576;
int main(int argc, char* argv[]){
short* sound;
int i;
int j;
bool status;
char* file_name;
sound = (short*) malloc( sizeof(short) * N_SAMPLE_PAIRS * N_CHANNELS );
if (!sound)
{
puts("Could not allocate space for the sound data.");
return (EXIT_FAILURE);
}
for (i = 0, j = 0; i < N_SAMPLE_PAIRS * N_CHANNELS; i += 2, j++ )
{
short datum1 = 450 * ((j >> 9 | j >> 7 | j >> 2) % 128);
short datum2 = 450 * ((j >> 11 | j >> 8 | j >> 3) % 128);
sound[i] = datum1; // One channel.
sound[i + 1] = datum2; // Another channel.
}
file_name = argc > 1 ? argv[1] : "Default.wav";
status = WriteWavePCM(sound, N_SAMPLE_PAIRS, file_name);
free(sound);
if (status)
{
printf("Discotheque is ready in \"%s\"\n", file_name);
}
else
{
puts( "Something seems to have gone wrong." );
return (EXIT_FAILURE);
}
return 0;
}
(播放生成的文件有什么用,VLC和QuickTime Player能够播放它;不确定其他媒体播放器。)
#1 楼
Endianness您的程序只能在小型Endian计算机上运行。如果您希望它在任何计算机上都能正常工作,则需要修复所有
fwrite()
调用以专门以小尾数格式进行输出。文件头。这样,您可以填写所有字段(以字节序正确的方式),然后执行一个fwrite()
。从代码看来,您可以将所有字段组合到一个标头中,但是如果更有意义,则可以有多个标头(RIFF标头,格式标头,数据标头等)。评论
\ $ \ begingroup \ $
是否有可能“填充”该结构(两个连续字段之间的孔)?如果是这样,会破坏事情吗?
\ $ \ endgroup \ $
–coderodde
15年9月22日在5:54
\ $ \ begingroup \ $
另外,我该如何处理字节序?谷歌没有太大帮助。
\ $ \ endgroup \ $
–coderodde
2015年9月22日下午6:27
\ $ \ begingroup \ $
@coderodde您确实需要注意结构填充,但是通常这些类型的标头是“正确”打包的,没有间隙。如果没有,则可以使用#pragma pack。为了提高字节序,您需要在littleendian机器上正常填充结构,或者在bigendian机器上以反向字节顺序填充结构。如果包含
\ $ \ endgroup \ $
– JS1
2015年9月22日下午6:53
#2 楼
fopen
在第二个参数中应该有一个b
。这主要是防止Windows用\n
替换会损坏文件的任何\r\n
字节。fopen( fileName, "wb" )
使fwrite参数的顺序首先与sizeof一致,然后与elements:
// Writing the RIFF header:
fwrite(&fChunkID, sizeof(fChunkID), 1, fout);
fwrite(&fChunkSize, sizeof(fChunkSize), 1, fout);
fwrite(&fFormat, sizeof(fFormat), 1, fout);
说到大小,不能保证标准基元的大小在所有平台上都完全相同(
long
是一个特殊的难题)。而是使用stdint.h
中的整数类型。#3 楼
实际上看起来还不错。一些较小的nitpicks您不应该转换
malloc
的返回值。fByteRate
和fBlockAlign
可以是const
,因为它们仅从const
值计算得出。 >我不太确定为什么WriteWavePCM
中的几乎所有变量都以f
为前缀。我想这是为了表明它们与正在写入文件的数据有关。由于此方法中的大多数代码都与将内容写入文件有关,这对我来说似乎很多余,而且有点分散注意力,但是YMMV。此处使用了魔术常数
fChunkSize = 36 + fSubchunk2Size;
-用命名常数替换(计算或定义的-取决于位置) 36来自36。fBitsPerSample
定义为16,假设short
是16位(因为它与将short* sound
数据写入文件有关)。并非在所有平台上都如此。因此,fBitsPerSample
应该是sizeof(*sound)
或要传入。使用不一致的支撑样式:
int main(int argc, char* argv[]){
vs
if (!sound)
{
您已经定义了
N_CHANNELS
,但是主循环被硬编码为以2
递增,因此这for (i = 0, j = 0; i < N_SAMPLE_PAIRS * N_CHANNELS; i += 2, j++ )
应该be
for (i = 0, j = 0; i < N_SAMPLE_PAIRS * N_CHANNELS; i += N_CHANNELS, j++)
#4 楼
除了在其他答案中给出的出色建议之外,我还建议彻底检查此循环:里面的常量。他们应该有一个明智的名字来描述他们的工作。
每当您看到此内容:您应该定义一个包含2条数据而不是一个数组的结构,在该结构中,您必须知道所有其他数据都是一个通道。诸如此类:
for (i = 0, j = 0; i < N_SAMPLE_PAIRS * N_CHANNELS; i += 2, j++ )
{
short datum1 = 450 * ((j >> 9 | j >> 7 | j >> 2) % 128);
short datum2 = 450 * ((j >> 11 | j >> 8 | j >> 3) % 128);
sound[i] = datum1; // One channel.
sound[i + 1] = datum2; // Another channel.
}
或者其他适合您的用例的东西。由于
struct
和i
现在相等,因此这也允许您只用一个控制变量编写循环。评论
\ $ \ begingroup \ $
我不知道魔术常数是做什么的。它们影响正在产生的“音乐”,但是我也不知道它们各自如何影响任何事情。
\ $ \ endgroup \ $
–coderodde
2015年9月22日下午6:05
\ $ \ begingroup \ $
“三年前,我编写了这个简单的C程序,用于将计算机生成的音乐写入到WAV文件中” vs.“我不知道魔术常数的作用。它们会影响所产生的“音乐”,但是我不知道他们每个人如何影响任何事情”-这让我感到奇怪,但是如果没有别的事情可以很好地证明评论的重要性...
\ $ \ endgroup \ $
–八位大师
2015年9月22日15:33
\ $ \ begingroup \ $
除了@ Eight-BitGuru所说的之外,我还可以告诉你其中至少有2个做什么。数学:450 *(任何百分比为128)将量化结果并进行缩放。 (任意百分比128)将值限制在0到128之间。由于您有16位样本,并且128个值适合7位,因此它非常安静,因此您需要将其缩放到介于0和2 ^ 16。通常,您会乘以512,但如果要混合使用450,则会为您提供一些余量。当然,通过将8位值缩放为16位,您还将留有空格(因此量化)。
\ $ \ endgroup \ $
–user1118321
2015年9月22日在16:11
#5 楼
没必要在同一条件下打包很多条件:if (!sound || !fileName || !(fout = fopen( fileName, "w" )))
很可能是: >
if (sound == NULL || fileName == NULL) {
return false;
}
FILE * fout = fopen(fileName, "wb");
if (fout == NULL) {
return false
}
个人喜好,但要避免在有条件的情况下进行作业,它们应该具有很好的可见性。我也更喜欢将
if
与NULL
进行比较,而不是将其几乎不可见的!
用作指针,以再次获得最大的可视性。如果参数为null或无法打开文件,则会丢失工作。没什么大不了的,但是仍然不是最理想的。 C99允许您在任何地方声明变量,因此可以将这些检查移至函数顶部。您在此处分配给
ws
: > ws = fwrite(sound, sizeof(short), pairAmount * N_CHANNELS, fout);
但是从未使用过该变量。我想这最初是为了进行错误检查?好吧,您不执行错误检查,这是非常乐观的。 Q4312079q可能会失败。您可以将它们设置为
fwrite
常数,以确保每次都不会在堆栈上重新分配它们。
评论
听起来像什么?某种技术。我不知道为什么,但是生成的音乐保持恒定的节奏。