用Python制作簡(jiǎn)單的鋼琴程序的教程
錄一段音頻,把它的音高改變50次并把每一個(gè)新的音頻匹配到鍵盤的一個(gè)鍵位,你就能把電腦變成一架鋼琴!
一段音頻可以被編碼為一組數(shù)值的數(shù)組(或者列表),像這樣:
我們可以在數(shù)組中每隔一秒拿掉一秒的值來將這段音頻的速度變成兩倍。
如此我們不僅將音頻的長(zhǎng)度減半了,而且我們還將它的頻率翻倍了,這樣使得它擁有比原來更高的音高(pitch)。
相反地,假如我們將數(shù)組中每個(gè)值重復(fù)一次,我們將得到一段更慢,周期更長(zhǎng),即音高更低的音頻:
這里提供一個(gè)可以按任意系數(shù)改變音頻速度的任意簡(jiǎn)單的Python函數(shù):
import numpy as np def speedx(sound_array, factor): """ 將音頻速度乘以任意系數(shù)`factor` """ indices = np.round( np.arange(0, len(snd_array), factor) ) indices = indices[indices < len(snd_array)].astype(int) return sound_array[ indices.astype(int) ]
這個(gè)問題更困難的地方在于改變音頻長(zhǎng)度的同時(shí)保持它的音高(變速,音頻拉伸(sound stretching)),或者在改變音頻的音高的同時(shí)保持它的長(zhǎng)度(變調(diào)(pitch shifting))。
變速
變速可以通過傳統(tǒng)的相位聲碼器(phase vocoder,感興趣的朋友可以讀一下維基百科的頁面)來實(shí)現(xiàn)。首先將音頻分解成重疊的比特,然后將這些比特重新排列使得他們重疊得更多(將縮短聲音的長(zhǎng)度)或者更少(將拉伸音頻的長(zhǎng)度),如下圖所示:
困難之處在于重新排列的比特可能很嚴(yán)重的互相影響,那么這里就需要用到相位變換來確保它們之間沒有影響。這里有一段Python代碼,取自這個(gè)網(wǎng)頁(打不開的話,您懂的?!g者注):
def stretch(sound_array, f, window_size, h): """ 將音頻按系數(shù)`f`拉伸 """ phase = np.zeros(window_size) hanning_window = np.hanning(window_size) result = np.zeros( len(sound_array) /f + window_size) for i in np.arange(0, len(sound_array)-(window_size+h), h*f): # 兩個(gè)可能互相重疊的子數(shù)列 a1 = sound_array[i: i + window_size] a2 = sound_array[i + h: i + window_size + h] # 按第一個(gè)數(shù)列重新同步第二個(gè)數(shù)列 s1 = np.fft.fft(hanning_window * a1) s2 = np.fft.fft(hanning_window * a2) phase = (phase + np.angle(s2/s1)) % 2*np.pi a2_rephased = np.fft.ifft(np.abs(s2)*np.exp(1j*phase)) # 加入到結(jié)果中 i2 = int(i/f) result[i2 : i2 + window_size] += hanning_window*a2_rephased result = ((2**(16-4)) * result/result.max()) # 歸一化 (16bit) return result.astype('int16')
變調(diào)
一旦你實(shí)現(xiàn)了變速以后,變調(diào)就不難了。如果需要一個(gè)更高的音高,可以先將這段音頻拉伸并保持音高不變,然后再加快它的速度,如此最后得到的音頻將具有原始音頻同樣的長(zhǎng)度,更高的頻率,即更高的音高。
把一段音頻的頻率翻倍將把音高提高一個(gè)八度,也就是12個(gè)半音。因此,要將音高提高n個(gè)半音的話,我們需要將頻率乘上系數(shù)2^(n/12):
def pitchshift(snd_array, n, window_size=2**13, h=2**11): """ 將一段音頻的音高提高``n``個(gè)半音 """ factor = 2**(1.0 * n / 12.0) stretched = stretch(snd_array, 1.0/factor, window_size, h) return speedx(stretched[window_size:], factor)
小程序:電腦鋼琴
讓我們來玩一下我們的變調(diào)器。我們先敲碗來確定一個(gè)“標(biāo)準(zhǔn)音高”:
[youku id="XNzM1NDM2NTky"]
接下來我們基于之前的音頻創(chuàng)造50個(gè)變調(diào)的音高,從很低到很高:
from scipy.io import wavfile fps, bowl_sound = wavfile.read("bowl.wav") tones = range(-25,25) transposed = [pitchshift(bowl_sound, n) for n in tones]
接下來根據(jù)這個(gè)文件中的順序,我們把每一個(gè)音頻匹配到鍵盤的一個(gè)鍵位,如下圖所示:
我們只需要在代碼中告訴計(jì)算機(jī)當(dāng)一個(gè)鍵按下來的時(shí)候播放其對(duì)應(yīng)的聲音,然后當(dāng)按鍵松開后停止播放就可以了:
import pygame pygame.mixer.init(fps, -16, 1, 512) # 太靈活了 <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_wink.gif" alt=";)" class="wp-smiley"> screen = pygame.display.set_mode((640,480)) # 設(shè)置焦點(diǎn) # 得到鍵盤的鍵位的正確順序的列表 # ``keys`` 如 ['Q','W','E','R' ...] 一樣排列 keys = open('typewriter.kb').read().split('\n') sounds = map(pygame.sndarray.make_sound, transposed) key_sound = dict( zip(keys, sounds) ) is_playing = {k: False for k in keys} while True: event = pygame.event.wait() if event.type in (pygame.KEYDOWN, pygame.KEYUP): key = pygame.key.name(event.key) if event.type == pygame.KEYDOWN: if (key in key_sound.keys()) and (not is_playing[key]): key_sound[key].play(fade_ms=50) is_playing[key] = True elif event.key == pygame.K_ESCAPE: pygame.quit() raise KeyboardInterrupt elif event.type == pygame.KEYUP and key in key_sound.keys(): key_sound[key].fadeout(50) # 停止播放并50ms淡出 is_playing[key] = False
就這樣我們把計(jì)算機(jī)變成了一臺(tái)鋼琴!至此,讓我為您表演一段土耳其進(jìn)行曲來表達(dá)對(duì)您耐心閱讀此文的謝意吧:
[youku id="XNzM1NDQ1MDA4"]
如果想自己試試的話,在這里可以下載你需要的所有文件。因?yàn)椴皇撬械娜硕加肞ython,我也用Javascript/HTML5(在這兒)實(shí)現(xiàn)了一臺(tái)電腦鋼琴,但是不是特別理想。如果有經(jīng)驗(yàn)豐富的HTML5/JS/elm程序員來改進(jìn)改進(jìn),或者從頭重寫就太好了。
接下來做什么?
更通常的情況下,我發(fā)現(xiàn)計(jì)算機(jī)很少被用來進(jìn)行表演性質(zhì)的演奏。我明白使用鋼琴鍵盤或者直接從樂器錄音會(huì)容易很多,但是請(qǐng)看看僅僅用一個(gè)碗和60行的Python代碼就能做到什么!
即便是很便宜的計(jì)算機(jī)也有如此多的控制來實(shí)現(xiàn)一個(gè)馬馬虎虎的音樂臺(tái):你可以對(duì)著麥克風(fēng)唱歌,對(duì)著攝像頭做手勢(shì),用鼠標(biāo)來調(diào)制,然后用鍵盤來完成剩下來的玩意兒。有如此多方式來表現(xiàn)自我,而每種方式又有那么一個(gè)Python包……有沒有具有藝術(shù)天賦的大神加入呀?
相關(guān)文章
在VScode中配置Python開發(fā)環(huán)境的超詳細(xì)指南
在使用VSCode編寫Python代碼前,我們需要先配置Python環(huán)境,這篇文章主要給大家介紹了關(guān)于在VScode中配置Python開發(fā)環(huán)境的相關(guān)資料,需要的朋友可以參考下2023-12-12Django model 中設(shè)置聯(lián)合約束和聯(lián)合索引的方法
今天小編就為大家分享一篇Django model 中設(shè)置聯(lián)合約束和聯(lián)合索引的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08Python的pywifi無線網(wǎng)絡(luò)庫的具體使用
pywifi是一個(gè)基于Python的用于操作無線網(wǎng)絡(luò)的庫,本文就來介紹一下pywifi的安裝及實(shí)際應(yīng)用場(chǎng)景使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02Python中的socket網(wǎng)絡(luò)模塊介紹
這篇文章主要介紹了Python中的socket網(wǎng)絡(luò)模塊介紹,Python 中,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07python pandas時(shí)序處理相關(guān)功能詳解
這篇文章主要介紹了python pandas時(shí)序處理相關(guān)功能詳解的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù)(實(shí)例代碼)
這篇文章主要介紹了python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01Python中的 is 和 == 以及字符串駐留機(jī)制詳解
這篇文章主要介紹了Python中的 is 和 == 以及字符串駐留機(jī)制詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-06-06PyCharm2020.1.1與Python3.7.7的安裝教程圖文詳解
這篇文章主要介紹了PyCharm2020.1.1與Python3.7.7的安裝教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Python isalpha()函數(shù)的具體使用方法詳解
這篇文章主要介紹了Python isalpha()函數(shù)的具體使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07