計算機(jī)經(jīng)常被用于處理音頻這種真實世界中的數(shù)據(jù)。聲音經(jīng)過采樣,量化和編碼后,存儲在音頻文件,如wav文件中。
文章首先介紹wave模塊的基礎(chǔ)用法; 再通過生成一定頻率聲波的算法實現(xiàn),來深入講解wave庫的使用。
wave模塊
wave
模塊提供了一個處理 wav 聲音格式的便利接口, 可獲取wav文件頭信息, 從文件讀取數(shù)據(jù), 也可直接將bytes
格式的數(shù)據(jù)寫入wav文件。
wave.open()
wave.open(file, mode=None)
類似于普通的打開文件,函數(shù)接收兩個參數(shù),file
為文件名或文件對象,mode
可取"r",“rb”,“w”,“wb"四個值,其中"r"和"rb”, "w"和"wb"效果完全相同。如下:
>>> wave.open('音樂.wav','r')
<wave.Wave_read object at 0x0355E810>
>>> wave.open('test.wav','w')
<wave.Wave_write object at 0x0355E810>
以讀模式打開的文件會返回Wave_read
對象, 寫模式打開時會返回Wave_write
對象。
Wave_read
Wave_read 對象通過wave.open() 函數(shù)創(chuàng)建。wave文件記錄了二進(jìn)制的音頻數(shù)據(jù),由許多幀組成,一個采樣對應(yīng)一個幀,每一幀長度為1或2字節(jié)。
Wave_read.getnchannels()
:返回聲道數(shù)量(1 為單聲道,2 為立體聲)
Wave_read.getsampwidth()
:返回采樣字節(jié)長度 (每一幀的字節(jié)長度)。
Wave_read.getframerate()
:返回采樣頻率。
Wave_read.getnframes()
:返回音頻總幀數(shù)。
Wave_read.getcomptype()
和Wave_read.getcompname()
:返回壓縮類型。
Wave_read.readframes(n)
讀取并返回以 bytes 對象表示的最多 n 幀音頻。
Wave_read.tell()
返回當(dāng)前文件指針位置。
Wave_read.setpos(pos)
設(shè)置文件指針到指定位置。
Wave_write
Wave_write 對象也通過wave.open() 函數(shù)創(chuàng)建。
Wave_write.setnchannels(n)
:設(shè)置聲道數(shù)。
Wave_write.setsampwidth(n)
:設(shè)置采樣字節(jié)長度為 n。
Wave_write.setframerate(n)
:設(shè)置采樣頻率為 n。
Wave_write.setnframes(n)
:設(shè)置總幀數(shù)為 n。(后來發(fā)現(xiàn)調(diào)用writeframes()時,wave模塊會自動更新總幀數(shù),實際上不需要調(diào)用這個函數(shù))
Wave_write.setcomptype(type, name)
:設(shè)置壓縮格式。(目前只支持 NONE 即無壓縮格式。)
Wave_write.tell()
返回當(dāng)前文件指針,其指針含義和 Wave_read.tell() 以及 Wave_read.setpos() 是一致的。
Wave_write.writeframes(data)
(或writeframesraw(data)
)
寫入bytes
格式的音頻幀,并更新 nframes。
Wave_write.close()
確保 nframes 是正確的,并在文件被 wave 打開時關(guān)閉它。 此方法會在對象收集時被調(diào)用。 如果輸出流不可查找且 nframes 與實際寫入的幀數(shù)不匹配時引發(fā)異常。
初步: 拼接音頻
程序先將兩段音頻中的數(shù)據(jù)讀入data1
和data2
中,再將讀取的數(shù)據(jù)拼接,寫入result.wav
。注意兩段音頻的采樣頻率、采樣字節(jié)長度需要一致。
import wave
sampwidth = 1
framerate = 22050
with wave.open('音樂1.wav','rb') as f1:
sampwidth = f1.getsampwidth()
framerate = f1.getframerate()
nframes1=f1.getnframes()
data1=f1.readframes(nframes1)
with wave.open('音樂2.wav','rb') as f2:
nframes2=f2.getnframes()
data2=f2.readframes(nframes2)
with wave.open('result.wav','wb') as fw:
fw.setnchannels(1)
fw.setsampwidth(sampwidth)
fw.setframerate(framerate)
#fw.setnframes(nframes1+nframes2)
fw.writeframesraw(data1)
fw.writeframesraw(data2)
初次實現(xiàn)
現(xiàn)在開始制作自己的聲音。程序生成一段頻率為200Hz, 長度為1.8秒的蜂鳴聲。
import wave
from winsound import PlaySound,SND_FILENAME
file = 'test.wav'
len_= 1.8 # 秒
frequency = 200
sampwidth = 1 #每一幀寬度(采樣字節(jié)長度)
framerate = 22050 # 采樣頻率 (越大音質(zhì)越好)
length = int(framerate * len_ * sampwidth)
para = [0b00000000]*(framerate//frequency//2*sampwidth)\
+[0b11111111]*(framerate//frequency//2*sampwidth) # 音頻的一小段
data=bytes(para)
# 生成wav文件
with wave.open(file,'wb') as f:
f.setnchannels(1)
f.setsampwidth(sampwidth)
f.setframerate(framerate)
# f.setnframes(length) (可選)
f.writeframes(data * (length // len(data)))
PlaySound(file,SND_FILENAME) # 播放生成的wav
再次實現(xiàn)
上述程序生成的是方波,并有一些缺陷,如para
中0b0000000
和0b11111111
的長度是整數(shù)且相同,導(dǎo)致生成的聲音頻率不精確,等等。
這里合成一段200Hz,長度為1.8秒的正弦波。
import wave,math
from winsound import PlaySound,SND_FILENAME
def generate(T,total,volume,sine=False):
# T: 周期, total 總長度, 都以幀為單位
if not sine:
h = T / 2
for i in range(total):
if i % T >= h:
yield volume
else:
yield 0
else:
# 計算方法: sin 的 T = 2*pi / w
w = 2 * math.pi / T; r = volume / 2
for i in range(total):
yield int(math.sin(w * i) * r + r)
file = 'test.wav'
len_= 1.8 # 秒
frequency = 200
sampwidth = 1
framerate = 22050
sine=True
volume = 255 # 音量, 0 - 255
data = bytes(generate(framerate / frequency, int(framerate*len_),
volume,sine)) # bytes能接收0-255整數(shù)型的迭代器
with wave.open(file,'wb') as f:
f.setnchannels(1)
f.setsampwidth(sampwidth)
f.setframerate(framerate)
f.writeframes(data)
PlaySound(file,SND_FILENAME)
運行程序會發(fā)現(xiàn),正弦波聽起來比方波更加柔和。
自己做的合成與Python內(nèi)置的音頻合成對比:
import winsound
# Beep(freq,duration),參數(shù)分別是頻率和毫秒為單位的持續(xù)時間
winsound.Beep(200,1800)
發(fā)現(xiàn), 前述程序很好地仿真了調(diào)用內(nèi)置的Beep
函數(shù)發(fā)聲。
但音質(zhì)有區(qū)別, 這是采樣字節(jié)長度為1(只有8位)導(dǎo)致的, 還需要加大采樣字節(jié)長度。
最終的程序如下:
import wave,math,struct
from winsound import PlaySound,SND_FILENAME
def generate(T,total,volume,sampwidth,sine=False):
# T: 周期, total 總長度, 以幀為單位
volume = min(volume * 2**(sampwidth*8),2**(sampwidth*8) - 1)
if not sine:
h = T / 2
for i in range(total):
if i % T >= h:
yield volume
else:
yield 0
else:
w = 2 * math.pi / T; r = volume / 2
for i in range(total):
# T = 2*pi / w
yield int(math.sin(w * i) * r + r)
file = 'test.wav'
len_= 1.8 # 秒
frequency = 200
sampwidth = 2
framerate = 22050
sine=True
volume = 255
# 8位的wav文件的一幀是無符號8位整數(shù), 而16位的一幀是有符號的整數(shù)(-32768至32767)。
if sampwidth == 1: # 8位
lst = list(generate(framerate / frequency, int(framerate*len_),
volume,sampwidth,sine))
data = bytes(lst)
elif sampwidth == 2:
data = b'' # 16位
lst = list(generate(framerate/frequency,
int(framerate*len_),
volume,sampwidth,sine))
for digit in lst:
data += struct.pack('<h',digit - 32768)
with wave.open(file,'wb') as f:
# --snip-- (看前面)
PlaySound(file,SND_FILENAME)
使用matplotlib
庫查看生成的聲波:文章來源:http://www.zghlxwxcb.cn/news/detail-408294.html
import matplotlib.pyplot as plt
# --snip--
plt.plot(range(len(lst)),lst)
plt.show()
寫在最后:
程序還可再做改進(jìn), 例如模擬各種樂器的音色, 也就是細(xì)微改變生成的聲波形狀。如果程序中加入共振峰, 還可實現(xiàn)簡單的語音合成?
但是, Windows系統(tǒng)已經(jīng)自帶了語音合成, 何必再開發(fā)一個呢?
下篇: Python 調(diào)用Windows內(nèi)置的語音合成,并生成wav文件文章來源地址http://www.zghlxwxcb.cn/news/detail-408294.html
到了這里,關(guān)于Python 從零開始制作自己的聲音 - wave模塊讀寫wav文件詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!