题面

题目数据标程包下载↓

众所周知的蜂鸣器

时间限制 : 10 Sec

空间限制 : 512 Mb

众所周知,Boshi是个喜欢弹琴的妹子。

TA经常在世界各地弹琴,比如APIO/CTSC赛场。

众所周知,[数据已删除]是个喜欢唱歌的妹子。

TA经常在世界各地唱歌,比如各种模拟考现场。

众所周知,Rayment是个喜欢电音的妹子。

TA经常在世界各地听电音,比如Logic Gatekeeper。

众所周知,XZY是个喜欢玩蜂鸣器的弱智。

TA经常在世界各地玩蜂鸣器,然后被劝退。

最近XZY又在玩蜂鸣器了,TA都玩了三年蜂鸣器了,从初中就开始玩了,玩到现在才玩出一个不着调的《极乐净土》,这让TA很难过。

TA曾经想,如果能直接把下载下来的音乐直接转化成控制蜂鸣器的代码就好了。

控制蜂鸣器的代码长这样:

Beep(a, b);

表示蜂鸣器以a的频率振动(单位为赫兹),连续振动b毫秒。

TA去世界各地查有没有这样的软件,但是众所周知,没有人会像TA这么无聊,所以没有。

TA也很想自己写一个,但是因为自己太弱了所以写不出,TA甚至都不知道音频文件的储存格式。

于是TA找啊找,找到了百度,找到了阿里巴巴,找到了四十大盗,找到了腾讯,找到了鲁迅,找到了枣树,找到了新中国。。。

最终TA在Google上找到了这样一段c++代码可以读入.wav格式的音乐(你可以不用读懂它在干什么的,等下XZY会向你说明的):

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

namespace WFR // Wave File Reader
{
    double Length;
    unsigned int num_samples;
    int low_limit   = 0l;
    int high_limit  = 0l;
    int BUF[2][2000005];
    int channels;
    struct HEADER
    {
        unsigned char   riff[4];                /* RIFF string */
        unsigned int    overall_size;           /* overall size of file in bytes */
        unsigned char   wave[4];                /* WAVE string */
        unsigned char   fmt_chunk_marker[4];    /* fmt string with trailing null char */
        unsigned int    length_of_fmt;          /* length of the format data */
        unsigned int    format_type;            /* format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law */
        unsigned int    channels;               /* no.of channels */
        unsigned int    sample_rate;            /* sampling rate (blocks per second) */
        unsigned int    byterate;               /* SampleRate * NumChannels * BitsPerSample/8 */
        unsigned int    block_align;            /* NumChannels * BitsPerSample/8 */
        unsigned int    bits_per_sample;        /* bits per sample, 8- 8bits, 16- 16 bits etc */
        unsigned char   data_chunk_header [4];  /* DATA string or FLLR string */
        unsigned int    data_size;              /* NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read */
    };
    unsigned char   buffer4[4];
    unsigned char   buffer2[2];
    struct HEADER header;

    void Read()
    {
        int read = 0;
        /* read header parts */
        read = fread( header.riff, sizeof(header.riff), 1, stdin );
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        /* convert little endian to big endian 4 byte int */
        header.overall_size = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( header.wave, sizeof(header.wave), 1, stdin );
        read = fread( header.fmt_chunk_marker, sizeof(header.fmt_chunk_marker), 1, stdin );
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        /* convert little endian to big endian 4 byte integer */
        header.length_of_fmt = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        header.format_type = buffer2[0] | (buffer2[1] << 8);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        channels = header.channels = buffer2[0] | (buffer2[1] << 8);
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        header.sample_rate = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        header.byterate = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        header.block_align = buffer2[0] | (buffer2[1] << 8);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        header.bits_per_sample = buffer2[0] | (buffer2[1] << 8);
        read = fread( header.data_chunk_header, sizeof(header.data_chunk_header), 1, stdin );
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        header.data_size = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        /* calculate no.of samples */
        num_samples = (8 * header.data_size) / (header.channels * header.bits_per_sample);
        int size_of_each_sample = (header.channels * header.bits_per_sample) / 8;
        /* calculate duration of file */
        Length = (float) header.overall_size / header.byterate;
        /* read each sample from data chunk if PCM */
        if ( header.format_type == 1 ) /* PCM */
        {
            int i = 0, size_is_correct = 1;
            char    data_buffer[size_of_each_sample];
            /* make sure that the bytes-per-sample is completely divisible by num.of channels */
            int bytes_in_each_channel = (size_of_each_sample / header.channels);
            if ( size_is_correct )
            {
                /* the valid amplitude range for values based on the bits per sample */
                switch ( header.bits_per_sample )
                {
                    case 8:
                        low_limit   = CHAR_MIN;
                        high_limit  = CHAR_MAX;
                        break;
                    case 16:
                        low_limit   = SHRT_MIN;
                        high_limit  = SHRT_MAX;
                        break;
                    case 32:
                        low_limit   = INT_MIN;
                        high_limit  = INT_MAX;
                        break;
                }
                for ( i = 1; i <= num_samples; i++ )
                {
                    read = fread( data_buffer, sizeof(data_buffer), 1, stdin );
                    if ( read == 1 )
                    {
                        /* dump the data read */
                        unsigned int    xchannels   = 0;
                        int     data_in_channel = 0;
                        for ( xchannels = 0; xchannels < header.channels; xchannels++ )
                        {
                            /* convert data from little endian to big endian based on bytes in each channel sample */
                            if ( bytes_in_each_channel == 4 )
                            {
                                data_in_channel = data_buffer[0] |
                                          (data_buffer[1] << 8) |
                                          (data_buffer[2] << 16) |
                                          (data_buffer[3] << 24);
                            }
                            else if ( bytes_in_each_channel == 2 )
                            {
                                data_in_channel = data_buffer[0] |
                                          (data_buffer[1] << 8);
                            }
                            else if ( bytes_in_each_channel == 1 )
                            {
                                data_in_channel = data_buffer[0];
                            }
                            BUF[xchannels][i] = data_in_channel;
                            /* check if value was in range */
                            if ( data_in_channel < low_limit || data_in_channel > high_limit )
                                printf( "**value out of range\n" );
                        }
                    }
                    else
                    {
                        printf( "Error reading file. %d bytes\n", read );
                        break;
                    }
                }       /*      for (i =1; i <= num_samples; i++) { */
            }               /*      if (size_is_correct) { */
        }                       /*  if (header.format_type == 1) { */
        else puts( "Your Wave File isn't in PCM Format!" );
    }
}

于是TA就能读入枣树,读入音乐了,TA找到了人生的曙光,人民的希望,宇宙的奥秘,哲学的真理。

可是TA还是不会把音乐转化成Beep,于是向你请教,并且告诉了你:

上面那段代码能这样调用:

  • WFR::Read(),无返回值,表示把.wav文件读入到WFR::BUF数组里
  • WFR::Length,双精度小数型,表示读入的歌曲时长,单位为秒
  • WFR::num_samples,无符号整型,表示整首歌的采样点个数
  • WFR::low_limit,整形,表示振幅的下界(一般你是用不到的,这个跟采样位宽有关)
  • WFR::high_limit,同上,表示振幅的上界(这里注意一下,振幅可以为负数,表示当前相位在初相位下方)
  • WFR::BUF[i][j],整形,表示声道$i$的第$j$个采样点的振幅,无单位,$i$从$0$开始标号,$j$从$1$开始标号
  • WFR::channels,整形,表示声道个数

上面的那段代码你可以自己随意修改。

如果你觉得上面那个namespace太垃圾了自己写一个也是完全没有问题的。

当XZY讲完这些以后,爱弹琴的Boshi终于忍不住了,告诉了你:

实际上XZY的歌曲都很垃圾,最多只有两个声道,有的只有一个声道。

所以如果有两个声道的话你可以直接取BUF[0][j]BUF[1][j]的算术平均值作为第$j$个采样点的振幅,这样就能把两个声道合成为一个声道了。

而且如果你把一个声道的振幅数组画成柱状图,你会发现它其实就是声音信号的“时域图”

对于单个音符,它的频率是单一的,固定的。

但是音乐里一般都有干扰,比如杂音。

XZY应该只是想要你把杂音去掉,再把最明显的频率给Beep出来而已。

取出最明显的频率这种操作,估计要在“频域图”上动手脚了!

Boshi讲完了这些,你恍然大悟,连连点头,欢天喜地,载兴载犇,僮仆欢迎,稚子候门,动手动脚,牛逼哄哄。

这时候,XZY又说:

其实我的音乐都很简单的,杂音都很少,在底下的提示里我会写好每个歌曲的特点的!

于是Boshi把XZY的歌都听了一遍,发现不是很牛逼,于是现场演奏了一首《两只老虎》,XZY赶紧收藏了。

这时候一直躲在旁边的[数据已删除]坐不住了:[数据已删除]天下第一!于是做现场的表演,演唱了一首《?♂郝&@瀚3#鸽》,在座的小朋友们都被TA精湛的演唱功底惊呆了,纷纷高兴地戳破了耳膜,XZY也欢欣鼓舞一路火花加闪电地把美妙的歌声录了下来,赶紧收藏了。

屏幕前聪明的你,能否帮助聋了的XZY和大家完成这个任务呢?

输入格式

一个.wav格式的文件,保证是pcm格式的。

另外:

此题需要开文件,从wave.in读入,输出到wave.out

请使用二进制方式打开文件,例如在int main()的开始部分添加:

freopen("wave.in", "rb", stdin), freopen("wave.out", "w", stdout);

如果不用”rb”方式打开文件可能会导致Error reading file(本机也是!)!请注意!

输出格式

若干行,每行两个非负整数$a$和$b$,表示Beep(a, b);

样例

请直接下载数据查看。

数据范围与提示

首先

输入文件是可以直接听的,下载下来以后把后缀名改为.wav,用音乐播放器打开就能听了。

你乐意用这个方法打表我很资磁

特殊问题

  1. 如果某一段时间原歌曲的声音很小,不足以判断出最明显的频率,请以前一段时间频率作为这一段时间的频率输出(若在歌曲开头处则频率直接设为0)。
  2. 由于蜂鸣器都很菜,所以所有连续时长小于$50$毫秒的Beep命令都无法发声(实际上如果运行的话会很奇怪,有的能发声有的不能),对于这种Beep命令XZY将视其Beep频率为0(实际上就变成了Sleep了)

判断答案正误

XZY写了个SPJ来判断你的输出是否正确

SPJ会依次模拟执行你的Beep命令,如果出现以下情况则直接判0分:

  • 命令个数超过$10 ^ 5$
  • 命令的总共执行时间(即播放时间)超过了$6\times 10 ^ 5$毫秒

如果上面两条都没有发生,则SPJ会用该测试点的答案文件进行判分:

  • 对于某一毫秒,如果两者的输出频率之差的绝对值小于25(单位赫兹),或者标准答案这一毫秒是没有声音的(频率为0),则判这一毫秒是正确的
  • 对于整首歌曲,如果有$40\%$及以上的时间是正确的(正确率高于$40 \%$),则给满分
  • 否则给出“满分$\times $正确率$\div 40 \%$”,并且分数向下取整

数据范围

歌曲时长最大为1分22秒

采样点数不超过$2 \times 10 ^ 6$个

其他基本不用操心

更详细的见下方“歌曲特性”

歌曲特性

数据编号 歌曲名称 特性
1 两只老虎 由蜂鸣器演奏,XZY用老人机录音
2 小酒窝 由蜂鸣器演奏,XZY用老人机录音
3 极乐净土 由蜂鸣器演奏,XZY用老人机录音
4 两只老虎 Boshi演奏,XZY用老人机录音
5 fc坦克大战BGM 红白机风格,网易云下载,时长4秒
6 马里奥地上关BGM 红白机风格,XZY用老人机录音
7 极乐净土 红白机风格,网易云下载
8 斗地主 红白机风格,网易云下载
9 Only My Railgun 红白机风格,网易云下载
10 ?♂郝&@瀚3#鸽 Warning, nuclear missle launched!

XZY的老人机型号是Nokia 216,单声道,采样率8000(采样率就是每秒的采样点数目)

网易云音乐下载的都是双声道,采样率不会高于11025

相关知识

傅里叶分析之掐死教程

第一组数据(两只老虎蜂鸣器版)的时域谱(横坐标对应时间,纵坐标对应振幅):

第一组数据的频域谱(横坐标对应频率,纵坐标对应振幅,高峰表示突出的频率):

题解

年度颓(du)废(liu)好题

感觉这个很好啊,应该可以算研究性学习了吧?

1. 采样与振幅

首先你要知道什么是“采样”。

一首歌曲保存下来不可能是连续的,只可能是保存这个歌曲在某些时间点的信息。

一般情况下都是保存的“振幅”

比如“采样率”是8000,表示的是每秒钟的歌曲保存8000个采样点,每个采样点代表了$\frac {1} {8000}$秒,每个采样点有一个有符号整数型值来储存它所代表的这一刻时间振膜的“相位”

“相位”说白了就是关于“初始相位”的位移,有正负号,“初始相位”就是振膜不通电的时候的位置。

因为$\frac {1} {8000}$秒十分短,所以这$\frac {1} {8000}$秒内的相位可以视作都是相同的,因此可以用一个整数代替这$\frac {1} {8000}$秒的相位。

显然,如果每秒采样次数越多,音质越高,也就是采样率越高音质越好,音乐文件也就越大。

(另外还有一个“采样定理”,内容就是如果采样率高于原信号频率的两倍,那么这个采样就能完全代表原信号)

题目中读入的WFR::BUF就是每个采样点的整数信息,如果你用图形化界面输出这个数组的话就和题面给出的“时谱图”是一样的:

它的横坐标是时间(也就是采样点编号),纵坐标是振幅(也就是每个采样点的数值)。

可以看到中间有一条横线,那个是振幅为0的位置,也就是“初始相位”的位置。

在这条横线上面的表示振膜向上运动,下面的表示向下运动。

2. 时谱图——频谱图

现在我们有了时谱图的相关姿势,来讲下频谱相关姿势

什么是频谱?

比如我给你一个函数图像:

你可以把它看成一个时谱图对吧,横坐标是时间,纵坐标是振幅,振幅随着时间变化而周期性变化

我现在请你求出该函数的表达式,保证该函数由一堆正弦波叠加而成。

当然对正弦波熟悉的同学,肯定可以一下子看出这是$f(x)= \sin (3 \pi x) + \sin (5 \pi x)$

但如果我给你的图像是:

这就比较无奈了对吧。。。(别试了这个有的频率都是小数了,而且我还在里面参了一个余弦波)

好了我们还是看回这个图像:

先说如果对这个图像进行频谱转换会得到什么:

你会得到这张图:

啥意思?

横坐标表示频率,纵坐标表示振幅。

它的意思是:你有一个振幅为$1$的,频率为$1.5$的周期函数(正弦波),还有一个振幅为$1$,频率为$2.5$的周期函数(正弦波)。

(其实正弦波和余弦波没啥区别,只不过是在时域上有一个相位差而已)

再看一个维基百科上的频谱分析的3D动画~

是不是很清晰明了了?

也就是说,你把你已知的时谱图转换成频谱图,就能分辨出原函数由哪些不同频率的正弦波/余弦波构成了!

当然还要说明一下,这个是对于周期函数说的,如果不是周期函数的话我们可以把它看成一个周期无限大的周期函数,然后再用正弦波/余弦波去拟合它即可。傅里叶大佬告诉我们,你用无限个正弦波余弦波总能拟合出来的(也比较显然对吧)

你看这个(正弦波叠加):

现在的问题就是——怎么进行把时谱图转换成频谱图?

Warning! XZY写了一整天也没想到怎么推出傅里叶级数公式,底下只是XZY写了一半,不忍心擦掉的部分。。。

好了,有请我们的装逼利器:欧拉公式!

$e ^ {ix} = \cos x + i \sin x$

$i$不必多讲吧,就是虚数单位,$i ^ 2 = -1$

所以。。。$e ^ {ix}$到底是什么呢?

我们把每一个三维坐标$(\sin x, \cos x, x)$画到三维空间里~

下图中红轴表示$\sin x$,绿轴表示$\cos x$,蓝轴表示$x$

(为了方便我把图形按红绿轴放大了$5$倍,按蓝轴缩小了$\pi$倍)

看!它升天了!它牛逼了!它载兴载犇,稚子候门,三径就荒,松菊犹存了!

这是什么意思呢?

红轴是$\sin x$,也就是虚数轴

绿轴是$\cos x$,也就是实数轴

蓝轴是$x$,也就是时间轴

任意两个轴都构成了一个平面,我们先看看它在虚数轴和时间轴构成的平面上的投影(也就是在红轴和蓝轴构成的平面上的投影):

哇,这不是一个有趣的、被旋转的正弦波吗?

再看看它在实数轴和时间轴构成的平面上的投影:

哇,这不是个有趣的、被旋转的余弦波吗?

(另外还有一个在虚实轴平面上的投影,显然就是个圆我都懒得放了)

嗯嗯,这可真是太棒了!

所以呢,你看啊:

$e ^ {ix} = \cos x + i \sin x$
$e ^ {-ix} = \cos x -i \sin x$

两式相加然后除个二得到:

$\cos x = \frac {e ^ {ix} + e ^ {-ix}} {2}$

欧耶,什么意思捏?

众所周知,$e ^ {ix}$表示一个随着时间变大而顺时针螺旋升天的奇妙卷卷。。。

那么$e ^ {-ix}$表示的就是一个随着时间变大而逆时针螺旋升天的奇妙卷卷呀!

= ̄ω ̄=

$e ^ {-ix}$如下图所示(可以拿去和上面的$e ^ {ix}$比♂较一下):

那他们俩加起来是什么效果呢?

如下(红卷卷是$e ^ {ix}$,蓝卷卷是$e ^ {-ix}$,绿色的是$2 \cos x$)

恩,就是这样的。

红色蓝色的虚部抵消了,就变成了绿色的波了。。。

(好吧其实上面这部分我只是想玩玩Geogebra)

然后类似的可以证明:$\sin x = \frac {e ^ {ix} – e ^ {-ix}} {2i}$

好的,XZY放弃了。。。XZY太弱了

将上面两个关于$\sin x$和$\cos x$的式子带入到三角形式的傅里叶级数中就能得到复指数形式的傅里叶级数啦!!!!!!!!!!!!_(:з」∠)_

(又疯了一个)

。。。

XZY真的不会了啊(╬▔皿▔)

恩,反正式子长这样:

其中:

为什么?我怎么知道啊啊啊啊啊啊啊啊啊啊啊~!别问我啊!问维基百科啊!

反正你对时谱图数组进行一遍正向傅里叶变换就得到频谱图啦!!!

这时得到的频谱图是整首歌的频谱图,它的高峰代表的是整首歌中出现明显的频率,并不知道它们什么时候出现的,所以要把原本的一首歌分成很多很多小段(我是每100毫秒分一个小段),每个小段进行一遍傅里叶变换,得到这一段的最明显频率再输出。

其中有一些问题——傅里叶变换以后我们得到的到底是什么?

你对$n$个采样点进行傅里叶变换,会得到$n$个点的结果,有$n$个复数。

其中第$1$个复数(也就是数组的第$0$位)代表的是0Hz,第$n$个复数(也就是数组的第$n-1$位)代表的是FsHZ,其中Fs表示你的采样率。

这很好理解,你傅里叶变换的数组的下标差是1,而你采样率是Fs,也就是数组里相邻的两项时间差是$\frac {1} {Fs}$秒,因此实际上你是把时间轴扩大了Fs倍再进行时谱图转频谱图的,你只要最后把下标乘个Fs弄回来就行了。

然后关于某个频率的振幅,也就是频谱图的高度,应当虚数的什么属性代表,这个问题困扰了我很久。。。

实际上与虚数的模长有关。

如果原信号中的振幅(峰值)为$P$,则FFT出来对应频率所对应的虚数的模长就是$\frac {P\times N} {2}$,其中$N$是FFT的长度。

大概是因为虚数模长代表了那个奇妙卷卷的半径。。。

(我记得网上还有个沙茶用实部作为振幅还发了一系列关于频谱分析的文章。。。)

最后还有一个问题:FFT的时候一般都是取$2 ^ k$格式的长度,那万一长度不整除怎么办呢?

实际上和多项式FFT没区别,在后面补零即可。经过试验,虽然补零会导致时域图变奇怪,但是补零不会太多,这样误差是完全可以忽略的。

这样就能愉快地打出代码啦!

分段进行FFT,然后频谱分析一下,看哪个频率振幅最大输出。。。

复杂度基本上是$O(n)$的

然后你会发现你的音乐有很多杂音(但其实因为SPJ为了能让大家过写的很水,所以应该可以直接AC),这样你就需要优化了。。。

3. 优化

这里提供几个XZY的XJB优化方法:

  • 时谱图中振幅低于最高振幅的$0.1$倍的直接判为噪音,振幅设为$0$,降低噪音
  • 不考虑低于$200$的频率,因为一般乐器不会低于200Hz(反正数据中没有)
  • 对于某一次Beep,如果它的上一次Beep和下一次Beep频率相同且它的时长比较短,则判断当前这次Beep是杂音,把当前这次的频率设成上次的频率
  • 最后合并一下相邻的且频率相似的Beep

这样基本上就能比较美妙地唱出数据中的那9首歌和一首[数据已删除]演唱的锟斤拷了。

代码:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

namespace WFR // Wave File Reader
{
    double Length;
    unsigned int num_samples;
    int low_limit   = 0l;
    int high_limit  = 0l;
    int BUF[2][2000005];
    int channels;
    struct HEADER
    {
        unsigned char   riff[4];                /* RIFF string */
        unsigned int    overall_size;           /* overall size of file in bytes */
        unsigned char   wave[4];                /* WAVE string */
        unsigned char   fmt_chunk_marker[4];    /* fmt string with trailing null char */
        unsigned int    length_of_fmt;          /* length of the format data */
        unsigned int    format_type;            /* format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law */
        unsigned int    channels;               /* no.of channels */
        unsigned int    sample_rate;            /* sampling rate (blocks per second) */
        unsigned int    byterate;               /* SampleRate * NumChannels * BitsPerSample/8 */
        unsigned int    block_align;            /* NumChannels * BitsPerSample/8 */
        unsigned int    bits_per_sample;        /* bits per sample, 8- 8bits, 16- 16 bits etc */
        unsigned char   data_chunk_header [4];  /* DATA string or FLLR string */
        unsigned int    data_size;              /* NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read */
    };
    unsigned char   buffer4[4];
    unsigned char   buffer2[2];
    struct HEADER header;

    void Read()
    {
        int read = 0;
        /* read header parts */
        read = fread( header.riff, sizeof(header.riff), 1, stdin );
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        /* convert little endian to big endian 4 byte int */
        header.overall_size = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( header.wave, sizeof(header.wave), 1, stdin );
        read = fread( header.fmt_chunk_marker, sizeof(header.fmt_chunk_marker), 1, stdin );
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        /* convert little endian to big endian 4 byte integer */
        header.length_of_fmt = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        header.format_type = buffer2[0] | (buffer2[1] << 8);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        channels = header.channels = buffer2[0] | (buffer2[1] << 8);
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        header.sample_rate = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        header.byterate = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        header.block_align = buffer2[0] | (buffer2[1] << 8);
        read = fread( buffer2, sizeof(buffer2), 1, stdin );
        header.bits_per_sample = buffer2[0] | (buffer2[1] << 8);
        read = fread( header.data_chunk_header, sizeof(header.data_chunk_header), 1, stdin );
        read = fread( buffer4, sizeof(buffer4), 1, stdin );
        header.data_size = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
        /* calculate no.of samples */
        num_samples = (8 * header.data_size) / (header.channels * header.bits_per_sample);
        int size_of_each_sample = (header.channels * header.bits_per_sample) / 8;
        /* calculate duration of file */
        Length = (float) header.overall_size / header.byterate;
        /* read each sample from data chunk if PCM */
        if ( header.format_type == 1 ) /* PCM */
        {
            int i = 0, size_is_correct = 1;
            char    data_buffer[size_of_each_sample];
            /* make sure that the bytes-per-sample is completely divisible by num.of channels */
            int bytes_in_each_channel = (size_of_each_sample / header.channels);
            if ( size_is_correct )
            {
                /* the valid amplitude range for values based on the bits per sample */
                switch ( header.bits_per_sample )
                {
                    case 8:
                        low_limit   = CHAR_MIN;
                        high_limit  = CHAR_MAX;
                        break;
                    case 16:
                        low_limit   = SHRT_MIN;
                        high_limit  = SHRT_MAX;
                        break;
                    case 32:
                        low_limit   = INT_MIN;
                        high_limit  = INT_MAX;
                        break;
                }
                for ( i = 1; i <= num_samples; i++ )
                {
                    read = fread( data_buffer, sizeof(data_buffer), 1, stdin );
                    if ( read == 1 )
                    {
                        /* dump the data read */
                        unsigned int    xchannels   = 0;
                        int     data_in_channel = 0;
                        for ( xchannels = 0; xchannels < header.channels; xchannels++ )
                        {
                            /* convert data from little endian to big endian based on bytes in each channel sample */
                            if ( bytes_in_each_channel == 4 )
                            {
                                data_in_channel = data_buffer[0] |
                                          (data_buffer[1] << 8) |
                                          (data_buffer[2] << 16) |
                                          (data_buffer[3] << 24);
                            }
                            else if ( bytes_in_each_channel == 2 )
                            {
                                data_in_channel = data_buffer[0] |
                                          (data_buffer[1] << 8);
                            }
                            else if ( bytes_in_each_channel == 1 )
                            {
                                data_in_channel = data_buffer[0];
                            }
                            BUF[xchannels][i] = data_in_channel;
                            /* check if value was in range */
                            if ( data_in_channel < low_limit || data_in_channel > high_limit )
                                printf( "**value out of range\n" );
                        }
                    }
                    else
                    {
                        printf( "Error reading file. %d bytes\n", read );
                        break;
                    }
                }       /*      for (i =1; i <= num_samples; i++) { */
            }               /*      if (size_is_correct) { */
        }                       /*  if (header.format_type == 1) { */
        else puts( "Your Wave File isn't in PCM Format!" );
    }
}

#include <bits/stdc++.h>

#define NS (2000005)
#define PI (3.14159265358979323846)

using namespace std;

typedef complex<double> cpx;

int n, rev[NS * 3];

double data[NS], rate;

cpx d[NS * 3];

void FFT(cpx (&a)[NS * 3], int N, int bs, int f)
{
    for (int i = 1; i < N; i += 1)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bs - 1));
    for (int i = 0; i < N; i += 1)
        if (i < rev[i]) swap(a[rev[i]], a[i]);
    for (int l = 1; l < N; l <<= 1)
    {
        cpx w0(cos(PI / l), sin(PI / l) * f);
        for (int i = 0; i < N; i += (l << 1))
        {
            cpx wn(1, 0), t1, t2;
            for (int j = i; j < i + l; j += 1, wn *= w0)
                t1 = a[j], t2 = wn * a[j + l], a[j] = t1 + t2, a[j + l] = t1 - t2;
        }
    }
    if (f == -1) for (int i = 0; i < N; i += 1) a[i] /= N;
}

double mus[NS];

int cnt = 0;

int main(int argc, char const* argv[])
{
    freopen("wave.in", "rb", stdin), freopen("wave.out", "w", stdout);
    WFR::Read(), n = WFR::num_samples, rate = 1.0 * n / WFR::Length;
    for (int i = 1; i <= n; i += 1)
    {
        for (int j = 0; j < WFR::channels; j += 1) data[i] += WFR::BUF[j][i];
        data[i] /= (double)WFR::channels;
    }
    double mx_voc = *max_element(data + 1, data + 1 + n);
    for (int i = 1; i <= n; i += 1)
        if (data[i] < 0.1 * mx_voc)
            data[i] = 0;
    #define STEP 100
    int sz = rate * STEP / 1000;
    int N = 1, bs = 0; while (N <= sz) N <<= 1, bs++;
    for (int i = 1; i + sz - 1 <= n; i += sz)
    {
        for (int j = 0; j < N; j += 1) d[j] = cpx(0, 0);
        for (int j = i; j <= i + sz - 1; j += 1) d[j - i + 1].real(data[j]);
        FFT(d, N, bs, 1);
        double mx = 0, sum = 0, tot = 0; int mi = 0;
        for (int j = 1; j <= (N >> 1); j += 1)
        {
            if (rate * j / N <= 200) continue;
            sum += sqrt(norm(d[j])), tot++;
            if (sqrt(norm(d[j])) > mx)
                mx = sqrt(norm(d[j])), mi = j;
        }
        if (mx < sum / tot * 5) mus[++cnt] = 0;
        else mus[++cnt] = rate * mi / N;
    }
    for (int i = 3; i <= cnt; i += 1)
        if (abs(mus[i] - mus[i - 2]) <= 10 && mus[i] > 1e-4)
            mus[i - 1] = mus[i];
    for (int i = 1; i <= cnt;)
    {
        double F = mus[i]; int sum = STEP; i++;
        while (i <= cnt && (abs(mus[i] - F) <= 10 || mus[i] < 1e-4))
        {
            if (mus[i] < 1e-4) mus[i] = F;
            sum += STEP, F = (mus[i] + F) * 0.5, i++;
        }
        printf("%d %d\n", (int)F, sum);
    }
    return 0;
}
分类: 文章

XZYQvQ

炒鸡辣鸡的制杖蒟蒻一枚QvQ