【音效处理】自制吉他软件效果器

        本文整理自信号与系统课程中所做的大作业,原理是基于python的音频接口实现实时的音频获取与播放,再对信号进行时域或频域上的处理。在本项目代码中总共实现了失真,降噪,混响,AMP,压缩五大模块的功能,可以将吉他(理论上是电吉他)接入电脑,自定义效果器参数输出,基本可以满足日常练琴使用,更多的功能也可以在源码框架上添加。

一、简介

在现代音乐演奏中,效果器是其中重要组成部分,其基本功能是将音频信号进行音效处理,包括放大,均衡,降噪,压缩,混响,失真等,然后将信号进行实时地输出,在各类演出场合,尤其是电吉他的演出中非常常见。最早这些声效并不来源于人们故意为之,而是录音故障或者音箱老化等带来的偶然效果。后来,在上世纪六七十年代,人们开始尝试用一些电路设备得到这些效果,也就是我们今天所说的效果器。

早期的效果器是硬件效果器,通过电路元器件对输入的音频信号进行处理然后进行输出。比如,60年代加州技师Red Rhoads为朋友制作的最早的Fuzz(一类失真)效果器,1966年博斯公司开发的第一款全电子效果器博斯音箱等。此类效果器在当今的音乐市场仍占据很大的比重,其优势是发展成熟,音质好,稳定,耐用是很多追求音质或特殊音色的音乐爱好者追捧的选择。但是,电子元件构成的硬件效果器往往比较昂贵,并且,一块效果器往往只能处理一种音效,而一个专门处理失真的效果器可能都要花费近千元,同时专业的音乐处理往往不止需要一块效果器。近年来,随着计算机和数字处理技术的发展,软件效果器开始变得流行起来。

软件效果器使用计算机的声卡将乐器的电信号转换为数字信号,通过一些处理数字信号的函数可以很方便地对数字信号进行处理。软件效果器最大的优势是便宜廉价,一台拥有声卡的个人电脑就足以进行处理,使用的软件通常收取的费用也很低,甚至有不少免费软件。软件效果器也有着很明显的缺点,Latency,翻译为延迟。从音频接口到进行输出,中间的延迟的多少决定了效果器是否好用。此外,受到音频采样,软件处理算法等种种因素的限制,软件效果器被认为音质效果不如硬件效果器。但是,随着计算机硬件的升级,声音处理算法的优化,甚至是人工智能在音乐处理中的应用,近年来的软件效果器进步明显。

在本项目中,我们会自制一款软件效果器,实现几个基本的效果器“单块”,源码级别的效果器可以自行组装链接,所有的参数算法全部透明化,可以自行调整实现更加丰富的音色。

二、低延迟的实时音频处理

为了降低效果器延时,提升“手感”,我们选用了python的pyAudio库来实现音频的获取与处理,我们以一定的采样率获取麦克风输入(其实接入的是配置好的乐器),然后采样一系列的数据点,进行处理后播放,python中的音频播放是非阻塞的,这意味着将音频输送操作系统后可以马上进行下一批取样,大大降低了时延问题。

import pyaudio
import struct
import numpy as np
import math
import queue
import copy
CHUNK = 882
CHANNELS = 1 

RATE = 48000  
pIn = pyaudio.PyAudio()


def fuc(inData):
    
    
    outData=inData
    return outData


def callbackIn(in_data, frame_count, time_info, status):

    #传入的data参数即是采样到的数据

    in_data_int = np.array(struct.unpack('<882h', in_data))  #out_data = in_data

    out_data_int = fuc(in_data_int)

    out_data_byte = struct.pack('<882h', *out_data_int) 

    return (out_data_byte, pyaudio.paContinue) 

    #return中的data就是会输出的内容



streamIn = pIn.open(format=FORMAT,

        channels=CHANNELS,

        rate=RATE,

        input=True,  #打开声音录制

        output = True, #打开声音播放
  
        frames_per_buffer=CHUNK,

        stream_callback=callbackIn) #非阻塞情况下使用回调函数


streamIn.start_stream()

while 1:  

    if streamIn.is_active():

        pass  

这里参考了pyaudio高级玩法3:实时声音处理 - 哔哩哔哩,这是本项目的一大基础,非常感谢老哥的分享。

三、音频处理函数

在获取了音频信号之后,我们要对这个音频信号进行处理,这里我们获取的音频信号是一个numpy的数组,每个元素为16位的整数。我们可以通过一些函数和算法进行处理,在此之前,我们先将信号转换为分贝表示,这是一种更加常见的音频信号描述方式:

V(t)=20 \lg \frac{\vert W(t) \vert}{W_{max}}

其中,W(t)为输入的十六位精度采样的源信号,W_{max}为16位有符号整数的最大值,V(t)表示输出用标准分贝表示的信号幅度。

反向变换类似,但由于用分贝表示幅度时没有正负号,我们需要额外保存这一点信号的正负符号Sign(W(t)),那么从V(t)恢复W(t)的过程可表示为:

W(t)=10^{V(t)/10}W_{max}Sign(W(t))

代码实现:

def Data2dB(inData):            #换算成PCB下的dB表示  大约-93~0dB
    maxV=32767   #16位有符号最大值
    #另外保留一个符号矩阵用于恢复
    signResult=np.sign(inData)
    ratio=np.abs(inData)/maxV
    outData=20*np.log10(ratio+0.000001)
    return outData,signResult

def dB2Data(inData,signResult):
    inData=inData/20
    inData=np.power(10,inData)
    inData=inData*32767
    inData=inData*signResult
    outData=inData.astype(np.int16) 
    return outData
1.Amp

Amp的全称是Amplifier,即音量放大器,在硬件效果中通常是由电子管音箱的箱头进行的。在数字信号中,我们只需要为源信号乘上一个大于1的常数就可以扩大音频信号的振幅了。

def Amp(inData,value):     #运算放大器,接在Dis前面
    outData = inData*value
    outData=outData.astype(np.int16)  #amp太大会削波,但它就是这个样子的,我们会用此进行失真
    return outData

值得注意的是,由于音频的采样深度的有上限的,最大值总是不能超过W_{max},超过这一大小的数值将被强制限制在W_{max},这就是削波。削波会导致音频的失真,这通常来说是不被欢迎的,但是,在电吉他演奏中,演奏者会通过这种特殊的音色来表达强冲击力的音乐感受,所以在下一部分我们会讨论如何得到更好的失真效果。

2.OverDriven/Distortion

OverDriven和Distortion意为过载和失真,他们通常是由于信号削波而带来的一种特殊音色。可以认为低程度的失真就是过载,高程度的过载就是失真,他们最早是吉他手将音箱的音量调至远超其额定音量的大小时得到。在效果器中,进行失真的单块通常加在Amp的后面以得到更好的失真效果。用信号处理的方法进行失真是容易的,需要将超过阈值的信号转变为阈值信号即可。

def Distortion(inData,limit):
    disData = np.where(np.abs(inData) > limit, np.sign(inData) * limit, inData)
    return disData
3.Compressor

Compressor即压缩器或者动态压缩器,它描述了音频中音量最大的部分和最小的部分的差异。在古典音乐中,有些乐句需要很大声来表达热烈的情绪,在另一些乐句需要比较小的声音来烘托抒情的气氛,但在流行音乐中则更加倾向于一个一致的比较大的音量来给人热烈的感受。压缩高音量部分,提升低音量部分是现代很多音乐制作者通式的做法,这也被称为“响度战争”。压缩效果的使用程度因人而异,但缺少这个效果会让乐声效果大打折扣,压缩效果是一个相对复杂的函数,其基本表达式为:

V_{cmp}(t)=V(t)+c(t)+M

离散化为:

V_{cmp}[n]=V[n]+c[n]+M

这里我们用音频信号的分贝表示来将相乘运算变为加法运算,M代表Make-up,是一个增益补偿,由于我们的压缩器降低了音频信号的幅度,需要加上一个增益补偿来保持输入输出的响度相当。问题的关键在于得到c[t],这个数值的计算需要两个模块:Level Detection 和 Gain Computer。

Gain Computer根据输入信号的电平(音量)来计算得到需要的增益。一种计算方法如下:

y_{G}=\begin{cases} x_{G} ,x_{G}\leq T\\ T+(x_{G}-T)/R,x_{G}>T \end{cases}

其中x_{G}为输入信号电平,y_G为输出信号电平,T为ThreShold,标志开始衰减的电平,R即Ratio是信号衰减率,当信号大于阈值时,超过阈值的部分会被线性衰减。

这个Gain Computer算法会导致信号变化不光滑,我们加入另一个参数Knee Width(W)来平滑化衰减函数:

y_{G}=\begin{cases} x_{G} ,2(x_{G}-T) \leq T\\ x_{G}+(1/R-1)(x_{G}-T+W/2)^2/(2W),2\vert x_{G}-T \vert \leq -W\\ T+(x_{G}-T)/R,2(x_{G}-T) >-W \end{cases}

这个模式叫做“Soft Knee”。

Level detection提供信号电平的平滑表示,这里引入另外的两个参数Attack time和Release time。他们表示信号处理的灵敏度,具体而言,考虑以下平滑滤波器:

s[n]=\alpha s[n-1]+(1-\alpha )r[n]

\alpha是滤波器系数,r[n]是输入信号,s[n]是输出信号,假设对这个系统输入阶跃信号,输出为:

s[n]=1-\alpha ^n

假设过了时间\tau之后,信号上升至1-\frac{1}{e},采样率是f_s,采样点数为采样率与采样时间的乘积,那么可以得到:

s[\tau f_s]=1-\frac{1}{e}

\alpha=e^{-1/(\tau f_s)}

也就是说,当我们的Attack time设置成200ms时,当我们输入阶跃信号之后,信号会在200ms上升至1-\frac{1}{e},Release time设置为200ms时,输入0电平信号,衰减至\frac{1}{e}需要200ms。

在此基础上我们制作Peak detection,它描述了信号在达到峰值之后会以何种方式下降。最基本的peak detector的差分方程为:

y_L[n]=\alpha_{R} y_{L}[n-1]+(1-\alpha_A)max(x_{L}[n]-y_{L}[n-1],0)

其中\alpha_A\alpha_R由Attack time和Release time根据式(12)算出,这个方法很基本,但有着显著的问题,Attack time与Release time相互影响导致信号失准,它可以进一步改进为:

y_1[n]=max(x_L[n],\alpha_Ry_1[n-1]+(1-\alpha_R)x_L[n])\\ y_L[n]=\alpha_Ay_L[n-1]+(1-\alpha_A)y_1[n]

称为smooth decoupled peak detector,是式(13)的改进,效果是使得Attack time与Release time的时间是准确的。Gain computer 和Level detector有多种连接方式,在本项目中,我们采用如下图所示的系统架构:

为了实现差分信号系统,我们需要为音频搭建一个缓冲区存储“过去”的音频信号,在这里我们使用一个npy数组即可满足压缩器的需求,而在下面的混响模块中我们会搭建更加复杂的缓冲区:

compressorBuffer=np.zeros(1)

压缩器:

def GainComupter(inData,ThreShold,Knee,Ratio):
    outData = np.zeros_like(inData)
    diff = inData - ThreShold  
      
    # 对于小于-Knee/2的情况  
    outData[2 * diff < -Knee] = inData[2 * diff < -Knee]  
      
    # 对于大于Knee/2的情况  
    outData[2 * diff > Knee] = ThreShold + diff[2 * diff > Knee] / Ratio
    
    between_indices = (diff >= -Knee/2) & (diff <= Knee/2)  
    if np.any(between_indices):  
        # 使用numpy的广播机制来避免显式循环  
          
        outData[between_indices] = inData[between_indices] + (1/Ratio - 1) * (diff[between_indices] + Knee/2)**2 / (2 * Knee)  
      
    return outData  
def Compressor(inData,Threshold,Ratio,AttackTime,ReleaseTime,MakeUp,KneeWidth):
    global compressorBuffer
    dbData,sigData=Data2dB(inData)
    outData=dB2Data(dbData,sigData)
    gainControl=GainComupter(outData,Threshold,KneeWidth,Ratio)
    #diffGainOutput=gainControl[:,881]
    diffGainOutput=gainControl
    diffGainOutput=np.concatenate((compressorBuffer, diffGainOutput))
     
    compressorBuffer=np.array([diffGainOutput[-1]])
    #print(compressorBuffer)
    diffGainOutput = diffGainOutput[:-1]
    levOut=LevelDetection(dbData,diffGainOutput,AttackTime,ReleaseTime)
    outData=dB2Data(levOut,sigData)
    #outData = inData
    return outData
4.Reverb

Reverb翻译为混响,声波在一个室内空间中会不断被墙壁等障碍物反弹,这些回声相互叠加会产生丰富的共鸣效果,这就是混响。在音乐演奏中,通常我们需要适当的混响来让乐音更加饱满,这也是古典音乐演奏通常在音乐厅或者大教堂举办的原因。在电声乐器的演奏中,人们很早就想到了模拟这种效果的方法——用一个弹簧盒子。在上世纪60-70年代,工程师就开始用一个装着弹簧的金属盒来产生混响效果,声波会在盒子中反弹,带动弹簧震动,然后产生带有一定金属质感的混响效果。在数字声效中的混响是通过模拟回声来进行的。1961年贝尔电话实验室的Schroeder建立了这个问题最早的数字解决方案,至今这个算法的效果仍然相当不错并且是许多改进算法的基石,这个算法用到两种制造回声的滤波器:梳状滤波器(Comb Filter)和全通滤波器(All-Pass Filter),篇幅受限我们不再推导其原理,而是给出系统表达式:

Comb Filter:

y(t)=x(t-\tau)+gy(t-\tau)

其中,x(t)是系统输入,y(t)是系统输出,g是一个控制幅度的常数。其效果是产生回声。

All-Pass Filter:

y(t)=-gx(t)+x(t-\tau)+gy(t-\tau)

另外我们取常数D=\tau \omega_s\omega_s为采样率。

Schroeder认为要达到混响效果,每秒至少需要1000个回声,因此他将四个Comb Filter先并联起来,每个Comb Filter的延迟不同,然后通过两个串联的All-Pass Filter来进一步增加回声,系统块状图如下:

此外,Kahrs 给出了Comb Filter的g值的计算方法,我们引入常量RT60,这是混响中的一个重要指标,衡量了声场衰减60dB所需要的时间,单位为秒,我们可以大致了解一些场地的RT60数值:

计算Comb Filter 中 g 的值的表达式为:

g=10^{-3DT_s/(RT_{60})}

除此之外,每个滤波器的延迟时间选择,RT60的选择,全通滤波器的增益选择也有一些经验公式。但整体而言,对于混响的理解因人而异,每个厂商都会声称自己做的混响是最好的混响效果(笑),在上述原理的框架下可以自行进行调节和处理。

代码实现:

首先我们要实现一个缓冲区,鉴于我们每次是采集一组numpy数据进行处理,但混响器的差分信号可能需要索引很久之前输入的信号,因此,我们使用python中的deque来实现非阻塞式的信号缓冲区,它会在运行时以FIFO原则对采集得到的数据进行换入换出,以单次采样的信号组作为单位。因为本项目只考虑简单的混响效果,在进行索引时会直接将开始的信号对应的信号组直接提出,并不会考虑更加复杂的拼接情况(那真让人头秃)。

buffer=queue.Queue(200)   #输入缓冲区队列
bufferY=queue.Queue(200)   #输出缓冲区
def ReadBuffer(tau):
    latency=int(tau*RATE)  
    index=int(latency/882)
    offset=latency%882
    start=882-offset
    cnt=0
    reIndex=buffer_size-1-index
    if(reIndex<0):
        reIndex=0;
    if(reIndex>buffer_size-2):
        reIndex=buffer_size-2
    #print(reIndex)
    #outlaten=buffer.queue[99-index] #粗糙版本
    outlaten=buffer.queue[reIndex]
    return outlaten

def CombFilter(inData,tau,gain):    #梳状滤波器   需要存储至少tau*RATE之前的状态
    buf_read1=ReadBuffer(tau) 
    buf_read2=ReadBufferY(tau) 
    outData=buf_read1+buf_read2*gain
    outData=outData.astype(np.int16)
    return outData
            
def AllPassFilter(inData,tau,gain):   #全通滤波器
    buf_read1=ReadBuffer(tau) 
    buf_read2=ReadBufferY(tau) 
    outData=-1*gain*inData+buf_read1+buf_read2*gain
    outData=outData.astype(np.int16)
    return outData
    
def Reverb(inData,RT60):                  #混响效果器
    gain0=10**((-3*0.61*RATE)/RT60)
gain1=10**((-3*0.47*RATE)/RT60)
    gain2=10**((-3*0.33*RATE)/RT60)
    gain3=10**((-3*0.23*RATE)/RT60)
gain4=10**((-3*0.13*RATE)/RT60)
gain5=10**((-3*0.91*RATE)/RT60)

    #gain1=0.8
    #gain2=0.8
    #gain3=0.8
    #gain4=0.8
    comb1=CombFilter(inData,0.47,gain1)
    comb2=-1*CombFilter(inData,0.33,gain2)
    comb3=CombFilter(inData,0.23,gain3)
comb4=-1*CombFilter(inData,0.13,gain4)
comb0=-1*CombFilter(inData,0.61,gain0)
comb5=-1*CombFilter(inData,0.91,gain5)

    outData=comb1+comb2+comb3+comb4+comb0+comb5
    gain=0.6
    outData=AllPassFilter(outData,0.010,gain)
    outData=AllPassFilter(outData,0.010,gain)
    
    return inData
四、使用方法

在func中调用以上所示的函数,例如一个clean的效果应该使用compressor和reverb,reverb可以效果增强一些。失真类:过载的削波比较小,可以调节比较高的削波门限,amp放大的倍数要略小一些,失真:更低的削波门限,更高的amp。失真和过载也会通入compressor和reverb(一点点),可以自行修改参数观察效果。

连接方式:

理论上来说可以用一个声卡将吉他的6.5mm输出转换成3.5mm输入,再通过声卡的另一个6.5mm输出来通入音箱等外置设备。但其实只要能找到6.5mm转3.5mm的设备就可以接入电脑了(比如一些DI或者综合效果器(真是多此一举)),不过没法留有输出导线,只能通过电脑音频播放器输出。

示例:

def fuc(inData):
    if(not buffer.full()):
        buffer.put(inData,False)
    else:
        buffer.get() #出队
        buffer.put(inData,False)   #入队
    #distortion:
    amp=1.5
    dislimit=6000
    Ratio=5
    ThreShold=-10
    Knee=0.0
    AttackTime=0.02
    ReleaseTime=0.2
    MakeUp=0
    outData=inData*1
    if(buffer.full()):
        test=Reverb(inData,0.8)
        #test=Compressor(inData,ThreShold,Ratio,AttackTime,ReleaseTime,MakeUp,Knee)
        #print(test)
        outData=test
    #dbData,sigData=Data2dB(inData)
    #outData=dB2Data(dbData,sigData)
    #outData=outData.astype(np.int16)
    #启动失真
    #outData = Amp(inData,amp)
    #outData=Distortion(outData,dislimit)
    outData=Compressor(outData,ThreShold,Ratio,AttackTime,ReleaseTime,MakeUp,Knee)
    

    #下面的代码无需修改
    if(not bufferY.full()):
        bufferY.put(outData,False)
    else:
        bufferY.get() #出队
        bufferY.put(outData,False)   #入队
    outData=NoiseGate(outData,500)
    outData=outData.astype(np.int16) 
    #print(outData)
    return outData

弹得不咋地,就不放演示了。失真和混响的效果还是相对明显的,除此之外也可以在这个框架下实现噪声门(类似压缩器),移调(需要频域变换)等效果,其他效果器的实现也基本类似,可以根据需求在这个基础上进行修改,不过和市面上更成熟的商业软件效果器肯定也没法比(那是自然)。

完整代码:

import pyaudio
import struct
import numpy as np
import pydub
import math
from pydub import AudioSegment
import queue
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import copy
CHUNK = 882 #需要是帧率的约数,太长会导致明显的延迟
#CHUNK=320
FORMAT = pyaudio.paInt16
buffer_size=200
buffer=queue.Queue(200)   #输入缓冲区队列
bufferY=queue.Queue(200)   #输出缓冲区
compressorBuffer=np.zeros(1)
CHANNELS = 1 

RATE = 176400  #非主流的帧率  #882/176400=0.005
#RATE=16000
pIn = pyaudio.PyAudio()
#plt.ion()  # Turn on interactive mode for real-time updating
#plt.figure(1)
#x = np.linspace(0,1, CHUNK)
#line, = ax.plot(x, np.zeros(CHUNK))
#ax.set_xlim(0, CHUNK)
#ax.set_ylim(-32768, 32767) 
# wait for stream to finish (5)
def update_plot(data):
    #line.set_ydata(data)
    plt.plot(x,data)
    plt.draw()
    #time.sleep(1)
    #fig.canvas.flush_events()
    
def Data2dB(inData):            #换算成PCB下的dB表示  大约-93~0dB
    maxV=32767   #16位有符号最大值
    #另外保留一个符号矩阵用于恢复
    signResult=np.sign(inData)
    ratio=np.abs(inData)/maxV
    outData=20*np.log10(ratio+0.000001)
    return outData,signResult

def dB2Data(inData,signResult):
    inData=inData/20
    inData=np.power(10,inData)
    inData=inData*32767
    inData=inData*signResult
    outData=inData.astype(np.int16) 
    return outData

def LevelDetection(inData,diffOutput,AttackTime,ReleaseTime):
    ArphaA=math.exp(-1/(AttackTime*RATE))
    ArphaR=math.exp(-1/(ReleaseTime*RATE))
    y1n=np.maximum(inData,ArphaR*diffOutput)
    outData=ArphaA*diffOutput+(1-ArphaA)*y1n
    return outData
    

def GainComupter(inData,ThreShold,Knee,Ratio):
    outData = inData
    diff = inData - ThreShold  
      
    # 对于小于-Knee/2的情况  
    outData[2 * diff < -Knee] = inData[2 * diff < -Knee]  
      
    # 对于大于Knee/2的情况  
    outData[2 * diff > Knee] = ThreShold + diff[2 * diff > Knee] / Ratio
    
    between_indices = (diff >= -Knee/2) & (diff <= Knee/2)  
    if np.any(between_indices):  
        # 使用numpy的广播机制来避免显式循环  
          
        outData[between_indices] = inData[between_indices] + (1/Ratio - 1) * (diff[between_indices] + Knee/2)**2 / (2 * Knee)  
      
    return outData  

def Compressor(inData,Threshold,Ratio,AttackTime,ReleaseTime,MakeUp,KneeWidth):
    global compressorBuffer
    dbData,sigData=Data2dB(inData)
    #outData=dB2Data(dbData,sigData)
    gainControl=GainComupter(dbData,Threshold,KneeWidth,Ratio)
    #diffGainOutput=gainControl[:,881]
    diffGainOutput=gainControl
    diffGainOutput=np.concatenate((compressorBuffer, diffGainOutput))
     
    compressorBuffer=np.array([diffGainOutput[-1]])
    #print(compressorBuffer)
    diffGainOutput = diffGainOutput[:-1]
    levOut=LevelDetection(dbData,diffGainOutput,AttackTime,ReleaseTime)
    outData=dB2Data(levOut,sigData)
    #outData = inData
    return outData


def Amp(inData,value):     #运算放大器,接在Dis前面
    #mask = np.abs(inData) < (32767/value)
    #print(mask)
    
    #inData = inData[mask]*value
    outData = inData*value
    outData=outData.astype(np.int16)  #amp太大会削波,但它就是这个样子的,我们会用此进行失真
    return outData

def Distortion(inData,limit):
    disData = np.where(np.abs(inData) > limit, np.sign(inData) * limit, inData)
    return disData

def NoiseGate(inData,limit):
    mask = np.abs(inData) < limit
    inData[mask] = 0
    outData=inData.astype(np.int16)  #降噪门,实现比较简单,待优化
    return outData

def ReadBuffer(tau):
    latency=int(tau*RATE)  
    index=int(latency/882)
    offset=latency%882
    start=882-offset
    cnt=0
    reIndex=buffer_size-1-index
    if(reIndex<0):
        reIndex=0;
    if(reIndex>buffer_size-2):
        reIndex=buffer_size-2
    #print(reIndex)
    #outlaten=buffer.queue[99-index] #粗糙版本
    outlaten=buffer.queue[reIndex]
    return outlaten
    
def ReadBufferY(tau):
    latency=int(tau*RATE)  
    index=int(latency/882)
    offset=latency%882
    start=882-offset
    cnt=0
    reIndex=buffer_size-1-index
    if(reIndex<0):
        reIndex=0;
    if(reIndex>buffer_size-2):
        reIndex=buffer_size-2
    
    #outlaten=buffer.queue[99-index] #粗糙版本
    outlaten=bufferY.queue[reIndex]
    return outlaten   
    

def CombFilter(inData,tau,gain):    #梳状滤波器   需要存储至少tau*RATE之前的状态
    buf_read1=ReadBuffer(tau) 
    buf_read2=ReadBufferY(tau) 
    outData=buf_read1+buf_read2*gain
    outData=outData.astype(np.int16)
    return outData
            
def AllPassFilter(inData,tau,gain):   #全通滤波器
    buf_read1=ReadBuffer(tau) 
    buf_read2=ReadBufferY(tau) 
    outData=-1*gain*inData+buf_read1+buf_read2*gain
    outData=outData.astype(np.int16)
    return outData
    
def Reverb(inData,RT60):                  #混响效果器
    gain1=10**((-3*0.47*RATE)/RT60)
    gain2=10**((-3*0.33*RATE)/RT60)
    gain3=10**((-3*0.23*RATE)/RT60)
    gain4=10**((-3*0.13*RATE)/RT60)
    #gain0=10**((-3*0.61*RATE)/RT60)
    #gain5=10**((-3*0.91*RATE)/RT60)
    #comb0=-1*CombFilter(inData,0.61,gain0)
    #comb5=-1*CombFilter(inData,0.91,gain5)

    
    #gain1=0.8
    #gain2=0.8
    #gain3=0.8
    #gain4=0.8
    comb1=CombFilter(inData,0.47,gain1)
    comb2=-1*CombFilter(inData,0.33,gain2)
    comb3=CombFilter(inData,0.23,gain3)
    comb4=-1*CombFilter(inData,0.13,gain4)
    outData=comb1+comb2+comb3+comb4
    #outData=comb1+comb2+comb3+comb4+comb0+comb5
    gain=0.6
    outData=AllPassFilter(outData,0.010,gain)
    outData=AllPassFilter(outData,0.010,gain)
    
    return inData

################################################

def fuc(inData):
    if(not buffer.full()):
        buffer.put(inData,False)
    else:
        buffer.get() #出队
        buffer.put(inData,False)   #入队
    #distortion:
    amp=1.5
    dislimit=6000
    Ratio=5
    ThreShold=-10
    Knee=0.0
    AttackTime=0.02
    ReleaseTime=0.2
    MakeUp=0
    outData=inData*1
    if(buffer.full()):
        test=Reverb(inData,0.8)
        #test=Compressor(inData,ThreShold,Ratio,AttackTime,ReleaseTime,MakeUp,Knee)
        #print(test)
        outData=test
    #dbData,sigData=Data2dB(inData)
    #outData=dB2Data(dbData,sigData)
    #outData=outData.astype(np.int16)
    #启动失真
    #outData = Amp(inData,amp)
    #outData=Distortion(outData,dislimit)
    outData=Compressor(outData,ThreShold,Ratio,AttackTime,ReleaseTime,MakeUp,Knee)
    
    if(not bufferY.full()):
        bufferY.put(outData,False)
    else:
        bufferY.get() #出队
        bufferY.put(outData,False)   #入队
    outData=NoiseGate(outData,500)
    outData=outData.astype(np.int16) 
    #print(outData)

    #在in 和 out 间做运算处理
    
    #update_plot(outData)
    return outData

###########################################################



def callbackIn(in_data, frame_count, time_info, status):

    #传入的data参数即是采样到的数据

    in_data_int = np.array(struct.unpack('<882h', in_data))  #out_data = in_data

    out_data_int = fuc(in_data_int)

    out_data_byte = struct.pack('<882h', *out_data_int) 

    return (out_data_byte, pyaudio.paContinue) 

    #return中的data就是会输出的内容



streamIn = pIn.open(format=FORMAT,

        channels=CHANNELS,

        rate=RATE,

        input=True,  #打开声音录制

        output = True, #打开声音播放

        frames_per_buffer=CHUNK,

        stream_callback=callbackIn) #非阻塞情况下使用回调函数



streamIn.start_stream()

while 1:  

    if streamIn.is_active():

        pass   



# stop stream (6)

streamIn.stop_stream()   

streamIn.close()



pIn.terminate() 

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值