《Python数字信号处理应用》学习笔记——第一章 声音和信号

专栏总目录

        信号代表随着时间变化的量。声音源于空气压力的改变。声音信号代表的是空气压力随着时间的变化。

        传声器是测量上述变化并产生表示所测声音的电信号的设备。传声器和扬声器都被称为换能器(transducer)。

1.1 周期信号

        周期信号是在一段时间之后重复出现的信号。比如:敲钟时候,钟会震动从而产生声音。录制后绘制其信号如下图:

                 

 图1-1

         该信号与三角函数类似,也就是说其形状和正选三角函数的形状一样。

        上图信号是周期性的,这里选择显示了3个完整的重复,也被称为循环。每个循环的时长称为周期,上图信号周期大概2.3ms。

        信号的频率是每秒钟内周期的数量,即周期的倒数。频率的单位是每秒钟循环次数,或者称为赫兹(Hertz),英文符号为”Hz“。

        上图信号频率约为439Hz,比国际标准音的440Hz稍低。这个音符的音名叫做A,或者更确切地说是小字一组a。

        音叉产生的是正弦信号,因为线性的变化是一种简单的谐波运动形式。大部分乐器产生的是周期信号,但是这些信号的形状不是正弦式的。如下图(小提琴演奏的片段):

                

图1-2

        上图信号是周期性的,但是信号的形状更加复杂。周期信号的形状被称为波形。大部分乐器产生的波形比正弦信号的复杂。波形的形状决定了音乐的音色也就是我们对声音品性的感受。对于比正弦信号更复杂的复杂波形,人们主观上认为它更加有内涵、更温暖而有趣。

1.2 频谱分析

        任何信号都可以表示成一系列不同频率的正弦信号的叠加和。

【数学概念】离散傅里叶变换(Discrete Fourier Transform, DFT),DFT也就是将信号转换成频谱。频谱是指相加产生信号的正弦波的集合。

【算法】快速傅里叶变换(Fast Fourier Transform, FFT),它是计算离散傅里叶变换的一种高效方式。

图1-3

        图1-3显示的是图1-2小提琴的频谱。x轴表示的是合成这个信号的频率范围,y轴表示各个频率元素的强度,或者说是振幅

其中频率最低的元素被称为基频。图1-2所示的信号的基频在440Hz左右(实际上要低一点)。

        此信号中,基频具有最高的幅度,所以它也是主频。通常情况下,感知到的声音的音高是由其基频决定的,即使它不是主频。

        频谱中其他峰值的频率还有880、1320、1760以及2200,它们是基频的整数倍。这些频率元素被称为谐波,因为它们在乐理概念上跟基频和谐:

        880Hz是小字2组a的频率,比基频高出一个八度。一个八度意味着基频频率的两倍。

        1320Hz接近小字3组e,也就是小字2组a的纯五度。

        1760Hz是小字3组a的频率,比基频高出2个纯八度。

        2200Hz接近小字4组#c,它是小字3组a向上大三度关系。

        这些谐波共同构成了A大调大三和弦A #C E,虽然不在一个八度内,其中一些音只是近似,因为西方音乐已经按照平均率做了调整。

 1.3 信号

1.3.1 获取thinkdsp包

        这本书提供的python模块thinkdsp中,包含信号和频谱分析的类和函数。下载地址:GitHub - AllenDowney/ThinkDSP: Think DSP: Digital Signal Processing in Python, by Allen B. Downey.

1.3.2 Signal类

        将使用到matplotlib包,请提前安装好

        安装方法:在cmd中输入下列命令:

pip install matplotlib

1.3.2.1 产生正、余弦信号

        thinkdsp中显示信号的类,名为Signal。这个类是多个信号类型的父类,包括Sinusoid中包含正弦和余弦信号。

        thinkdsp提供产生正弦和余弦信号的函数,调用方法如下:

from thinkdsp import CosSignal, SinSignal


# 产生一个频率440Hz、振幅为1、无偏移量的余弦信号
cos_sig = thinkdsp.CosSignal(freq=440, amp=1.0, offset=0)
# 产生一个频率880Hz、振幅为0.5、无偏移量的正弦信号
sin_sig = thinkdsp.SinSignal(freq=880, amp=0.5, offset=0)

        其中,freq——频率,单位Hz;amp——振幅,单位未指定;offset(偏移量)是指信号的初始值,它定义了正弦波在t=0时的初始位置,offset=0表示正弦信号从0开始。

        在jupyter notebook中,可以在执行上面命令基础上,继续执行以下命令,显示信号图。

from thinkdsp import decorate

cos_sig.plot()
decorate(xlabel='Time (s)')

        在idle中,或在.py文件中,可以通过以下命令生成图片,便于查看

from thinkdsp import CosSignal, SinSignal, decorate

# 按照要求生成余弦和正弦信号
cos_sig = CosSignal(freq=440, amp=1.0, offset=0)
sin_sig = SinSignal(freq=880, amp=0.5, offset=0)

# 生成余弦信号图
cos_sig.plot()
decorate(xlabel='Time (s)')

# 将信号图保存为cosine_signal.png文件
import matplotlib.pyplot as plt
plt.savefig('cosine_signal.png')

# 清除已生成的信号图,为下一步做准备
plt.clf()

# 生成正弦信号图
sin_sig.plot()
decorate(xlabel='Time (s)')

# 将信号图保存为sine_signal.png文件
plt.savefig('sine_signal.png')

1.3.2.2 信号叠加和操作

        各个信号都有__add__方法,所以可以对他们直接使用 ”+“ 操作:

   mix = sin_sig + cos_sig

        其结果为SumSignal,表示了两个或者多个信号的叠加和。

         在jupyter notbook中,在上述信号生成后,继续输入以下命令,实现信号叠加操作

mix = sin_sig + cos_sig
# 生成信号叠加图
mix.plot()
decorate(xlabel='Time (s)')

         在idle中,或在.py文件中,可以通过以下命令生成图片,便于查看

from thinkdsp import CosSignal, SinSignal, decorate
import matplotlib.pyplot as plt

# 按照要求生成余弦和正弦信号
cos_sig = CosSignal(freq=440, amp=1.0, offset=0)
sin_sig = SinSignal(freq=880, amp=0.5, offset=0)

# 信号叠加操作
mix = sin_sig + cos_sig

# 生成信号叠加图
mix.plot()
decorate(xlabel='Time (s)')

# 将信号图保存为mix_signal.png文件
plt.savefig('mix_signal.png')

         Signal实际上是数学函数的Python表示。大部分信号(Signal)在全部的t值下做了定义——从负无穷到正无穷。

1.3.2.3 Wave对象

        这一步操作用到scipy包,需要提前按装。

        安装方法:在cmd窗口中输入下列命令:

pip install scipy

        对Signal求值,即根据一系列时间点ts,来计算出对应信号值ys。在Wave对象中使用封装了表示ts和ys的Numpy数组。

        Wave表示的是信号在一些列时间点下求出的值。每个时间点被称为(借用了电影和视频的概念)。测度本身被称为样本,虽然 ”帧“ 和 ”样本“ 在一些情况下可以互换使用。

        Signal提供了make_wave,后者返回一个新的Wave对象:

wave = mix.make_wave(duration=0.5, start=0, framerate=11025)

        duration——Wave的长度,单位为秒;start——开始的时间,单位秒;framerate——每秒帧数,该值为整数,表示每秒内的样本数。

        11025帧每秒,是在音频文件中最常用的几个帧率之一,这些音频文件包括波形音频文件(Wavefrom AudioFile,WAV)和MP3。

        上述代码产生的信号从t=0到t=0.5,取得5513个间距相等的帧(5513是11025的一半)。帧间的时间差被称为时间步长,值为1/11025秒,约等于0.0000907秒。

        在jupyter notbook中,生成信号后,使用IPython.display包中的Audio函数,可以将信号转换为音频,进行聆听

wave = mix.make_wave(duration=0.5, start=0, framerate=11025)


# 【第一种】获取音频方式
from IPython.display import Audio
audio = Audio(data=wave.ys, rate=wave.framerate)
audio

# 【第二种】获取音频方式
wave.make_audio()

         jupyter notebook中使用两种获取音频方式,显示如下图:

        在idle中,或在.py文件中,可以通过以下命令生成该音频,便于聆听

from thinkdsp import CosSignal, SinSignal, decorate
import numpy as np

# 创建信号
cos_sig = CosSignal(freq=440, amp=1.0, offset=0)
sin_sig = SinSignal(freq=880, amp=0.5, offset=0)

# 叠加信号
mix = sin_sig + cos_sig

# 生成叠加信号音频数据
wave = mix.make_wave(duration=0.5, start=0, framerate=11025)

# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)

# 现在,您可以使用音频播放器打开并播放output.wav文件。

1.3.2.4 Wave的plot方法

        Wave提供的plot方法,使用方法为pyplot,可以用于绘制波形。

wave.plot()
pyplot.show()

        pyplot是matplotlib的一部分。Python默认安装,可以直接调用。

        对于freq=440,在0.5秒之内有220个周期,所以其绘制结果开起来会像一大块色块。要放大显示几个周期的波形可以使用segment,它复制了Wave的一段,然后返回一个新波形:

period = mix.period
segment = wave.segment(start=0, duration=period*3)

        period是Signal的属性,它返回的是信号的周期,单位为秒;start和duration的单位都是秒。这个例子从mix中复制了前3个周期,其结果是一个Wave对象。

        如果绘制segment,它的结果如下图1-4所示,这个信号包含两个频率元素,所以它比饮茶的信号更为复杂,但是比小提琴的简单。

 

图1-4 两个三角函数混合信号的片段

         在jupyter notbook中,在上述信号生成后,显示命令如下:

segment.plot()
decorate(xlabel='Time (s)')

        在idle中,或在.py文件中,可以通过以下命令生成该图:

from thinkdsp import CosSignal, SinSignal, decorate

import matplotlib.pyplot as plt

# 创建信号
cos_sig = CosSignal(freq=440, amp=1.0, offset=0)
sin_sig = SinSignal(freq=880, amp=0.5, offset=0)

# 信号叠加操作
mix = sin_sig + cos_sig

# 生成叠加信号音频数据
wave = mix.make_wave(duration=0.5, start=0, framerate=11025)

# 从mix中复制了前3个周期
period = mix.period
segment = wave.segment(start=0, duration=period*3)

segment.plot()
decorate(xlabel='Time (s)')


# 将信号图保存为mix_signal_2.png文件
plt.savefig('mix_signal_2.png')

1.3.2.5 作者在学习资料中的jupyter notebook测试中进行的其他操作

(1)wave.normalize()音量最大化
# 放大一个波形,使其峰值匹配音量做大值,不需要保存为变量,执行后wave就已经被调整
wave.normalize()

        在jupyter notebook中显示normalize后的图

wave.plot()
decorate(xlabel='Time (s)')

         在idle中,或在.py文件中,可以通过以下命令生成该图:

from thinkdsp import CosSignal, SinSignal, decorate

import matplotlib.pyplot as plt

# 创建信号
cos_sig = CosSignal(freq=440, amp=1.0, offset=0)
sin_sig = SinSignal(freq=880, amp=0.5, offset=0)

# 信号叠加操作
mix = sin_sig + cos_sig

# 生成叠加信号音频数据
wave = mix.make_wave(duration=0.5, start=0, framerate=11025)

# 音量最大化处理
wave.normalize()

wave.plot()
decorate(xlabel='Time (s)')


# 将信号图保存为normalized_signal.png文件
plt.savefig('normalized_signal.png')

(2)淡入淡出wave.apodize()
# 淡入淡出处理
wave.apodize()

        显示波形图,可以在音量最大化操作后增加上述淡入淡出处理后显示。具体方法不再赘述,参考上一步。

1.4 波形读写

1.4.1 读取文件

        thinkdsp提供的read_wave可以读取WAV文件并返回一个Wave对象:

violin_wave = thinkdsp.read_wave('input.wav')

 测试脚本:

# 在下载资料包的code文件中有92002__jcveliz__violin-origional.wav测试音频
filename = '92002__jcveliz__violin-origional.wav'

# 如果在当前目录中没有找到92002__jcveliz__violin-origional.wav文件,则执行下载命令
if not os.path.exists(filename):
    !wget https://github.com/AllenDowney/ThinkDSP/raw/master/code/92002__jcveliz__violin-origional.wav

from thinkdsp import read_wave

# 读取该.wav格式文件
wave = read_wave(filename)

        jupyter notebook中,通过继续下列命令播放

wave.make_audio()

        在idle中,或在.py文件中,通过继续执行下列命令获得音频文件

# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)

# 现在,您可以使用音频播放器打开并播放output.wav文件。

        利用上面学过的segment方法,从以上音频中截取一段,开始时间为1.2,持续时间duration为0.6

start = 1.2
duration = 0.6
segment = wave.segment(start, duration)

        jupyter notebook显示,继续执行下列命令

segment.plot()
decorate(xlabel='Time (s)')

        idle或在.py文件中执行,继续执行下列命令

import matplotlib.pyplot as plt
from thinkdsp import decorate

segment.plot()
decorate(xlabel='Time (s)')

# 将信号图保存为segment_signal.png文件
plt.savefig('segment_signal.png')

 

1.4.2 写入文件

        Wave提供了write函数,它能写WAV文件:

wave.write(filename='output.wav')

        可以通过媒体播放器播放WAV文件从而听到这个文件的声音。在Linux系统上我使用的是aplay,他是一个简单鲁棒(适应性好)的播放器,很多Linux发行版都有它。

1.4.3 在Unix系统上使用aplay播放器,作为子进程播放文件

        thinkdsp同时还提供play_wave,它能以子进程运行媒体播放器(在Unix系统上使用aplay播放器测试):

thinkdsp.play_wave(filename='output.wav', player='aplay')

1.5 频谱

        Wave提供了make_spectrum,它返回的是Spectrum:

spectrum = wave.make_spectrum()

        [操作案例]:

        延续1.4.1的segment操作数据,获取segment的频谱图

spectrum = segment.make_spectrum()

         jupyter notebook显示方法:

spectrum.plot()
decorate(xlabel='Frequency (Hz)')

       使用idle或.py文件的输出显示图的方法:

import matplotlib.pyplot as plt
from thinkdsp import decorate

spectrum.plot()
decorate(xlabel='Time (s)')

# 将信号图保存为spectrum.png文件
plt.savefig('spectrum.png')

        

        就像上述操作表现得那样,Spectrum提供了plot,执行下列代码,也可以实现显示上述图像:

import thinkplot
spectrum.plot()
thinkplot.show()

 

        作者提供的thinkplot模块,提供了pyplot中一些函数的封装。 在使用thinkplot.show()之前,要安装pandas库

安装方法:在cmd命令窗口,输入以下命令

pip install pandas

        可以通过调整spectrum.plot(high=1000)中,high参数值,调整x轴显示范围

Spectrum提供了3种修改频谱的方法:

(1)low_pass,它加载一个低通滤波器,也就是说低于某个截止频率的频率元素按照一定因数衰减(也就是在大小上降低)了。

(2)high_pass,它加载了一个高通滤波器,也就是说低于某个截止频率的元素被衰减量。

(3)band_stop,它让处于两个截止频率之间的波段内的频率元素衰减了。

下面的例子将所有高于600的频率衰减了99%:

spectrum.low_pass(cutoff=600, factor=0.01)

        低通滤波器会移除明亮的高频声音信号,所以其结果的声音比较压抑而且昏暗。读者可以将Spectrum转化回Wave,然后播放来感受以下:

wave = spectrum.make_wave()
wave.play('temp.wav')

        play方法将波形写入一个文件并播放它。如果读者使用Jupyter Notebook,就可以使用make_audio,它会创建一个音频部件用于播放声音。

1.6 波形对象

        thinkdsp.py里面提供的大部分函数只是对Numpy和Scipy函数的稀薄封装(thin wrappers)。

thinkdsp的初始库包括Signa、Wave和Spectrum:

        给定Signal,就可以创建一个Wave;

        给定Wave,就能创建一个Spectrum;

        反之亦然。

Wave对象包括3个特性:

        包含信号参数的Numpy数组ys;

        信号开始采样和取值的时间点数组ts;

        每单位时间的采样数framerate。(时间单位通常是“秒”,但也可以不是,比如“天”)

        Wave还提供了3个只读属性:start、end和duration。如果读者修改了ts,这些属性也会相应改变。

可以通过直接修改ts和ys来改变波形,例如:

wave.ts *= 2

wave.ys += 1

        第一行波形扩大了2倍,会听起来更响亮,第二行将波形按时间移动了,让它晚一秒开始。

        此外,Wave还提供了执行通用操作的方法。例如,与上面相同的两个变换可以写成以下形式:

wave.scale(2)

wave.shift(1)

        可以在http://greenteapress.com/thinkdsp.html获取相关文档。

1.7信号对象

        Signal是一个父类,它向各种信号提供通用的函数,比如make_wave。子类继承了这些方法并提供evaluate,也就是在给定的时间序列内对信号取值。

        例如,Sinusoid是Signal的子类,定义如下:

class Sinusoid(Signal):
    def __init__(self, freq=440, amp=1.0, offset=0, func=np.sin):
        Signal.__init__(self)
        self.freq = freq
        self.amp = amp
        self.offset = offset
        self.func = func

__init__的参数有:

freq        频率,其含义为每秒周期数,单位是Hz。

amp        幅度。幅度的单位比较随意,通常设定为1.0,对应为传声器的最大输入或给扬声器的最大输出。

offset        其含义为信号周期的起始。offset的单位为弧度。

func        它是一个Python函数,用来对指定时间点的信号求值。通常它不是np.sin就是np.cos,对应的分别是正弦信号和余弦信号。

        跟很多初始化方法一样,它也是把参数存起来以备未来使用。

        Signal提供了make_wave,如下所示:

def make_wave(self, duration=1, start=0, framerate=11025):
    n = round(duration * framerate)
    ts = start + np.arange(n) / framerate
    ys = self.evaluate(ts)
    return Wave(ys, ts, framerate=framerate)

        start 和 duration 分别表示开始和持续的时间,单位为秒。framerate 是每秒帧数(采样数)。

        n 代表采样的数量,而 ts 是采样时间的 NumPy 数组。

        为计算 ys,make_wave 需要引用 evaluate,它是由 Sinusoid 提供的:

def evaluate(self,ts):
    phases = PI2 * self.freq * ts + self.offset
    ys = self.amp * self.func(phases)
    return ys

下面来逐步分析这个函数

1. self.freq 是频率,代表每秒周期数,而ts的各个元素都是以秒计的,所以它们的乘积是从起始时间开始的周期数。

2. PI2是一个常数,其值为2π。乘上PI2之后就把周期转换成了相位。读者可以把相位理解为弧度形式的“从起始时间开始的周期数”,而每个周期的弧度是2π.

3. self.offset 是t = 0 时刻的相位。其作用是吧信号在时域上向左或向右移动一定距离。

4. self.func 是 np.sin或者np.cos,其值便是处于-1 ~ 1。

5. 乘上self.amp 之后产生的信号范围为 -self.amp到self.amp。

在数学意义上,evaluate 的形式如下:

        其中A是幅度,f是频率,t是时间,是相位偏移。这里看起来像是写了大段代码,而仅仅为了一个简单的表达式求值,但是正如我们将要看到的,这段代码提供了处理所有信号的架构,而不仅是三级函数信号的。

写在本章最后:

        本章我们了解了thinkdsp以及thinkplot及wave的基本操作,建议在播放音频前进行如下预处理。

在jupyter notebook中操作如下:

# 音量最大化
wave.normalize()
# 淡入淡出
wave.apodize()

# 展示图示
wave.plot()
decorate(xlabel='Time (s)')

        执行播放

wave.make_audio()

 下一篇:《Python数字信号处理应用》学习笔记——第二章 谐波

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

静候光阴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值