,我发现了一个开源模块比较适合入门:【PySynth 】 很有趣。
这里记录一下我的研究笔记。——如果有直接的数学模型就好了,可能得去查声波的书。
下载下来是这样的:
文件夹里:menv.py是帮助文件;mixfiles.py是混音,pysynth.py是个模型。
我先不看具体怎么来的,试一试应用。
先看看备注:
'song' 是一个被定义的列表或元组,格式是这样 ['音', 长度]
音符是'a','g'这些; 升半音以 '#' 表示,降半音以 'b' 表示;以octave 结束 (默认为四分音符);asterisk 在最后代表重音; 'r' 是空.
音的长度用数字表示:1=全音符; 2=二分音符; 4=四分音符, 等.
浮点音符写法:
1.33 = -2 = 二分浮点音符
2.66 = -4 = 四分浮点音符
5.33 = -8 = 八分浮点音符
一些参数:
节奏:每分钟节拍数; bpm = 95
八度转变 (neg. 降八度; pos. 升八度); transpose = 0
音符间停顿 (0. = 连音 ; 0.5 = 断音); pause = 0.05
Volume boost:音量变高 (1. = 音量无变化); boost = 1.2
Output file name 输出文件名;fn = 'pysynth_output.wav'
其他参数:
Influences the decay of harmonics over frequency. Lowering the value eliminates even more harmonics at high frequencies.
Suggested range: between 3. and 5., depending on the frequency response of speakers/headphones used
harm_max = 4.
直接到应用部分,它用的样品音乐,来源于demosongs.
# 产生样品音乐
if __name__ == '__main__':
print()
print("Creating Demo Songs... (this might take about a minute)")
print()
# Nocturne Op. 9 #2 by F. Chopin的开头
song3 = (
('bb', 8),
('g5*', 2), ('f5', 8), ('g5', 8), ('f5', -4), ('eb5', 4), ('bb', 8),
('g5*', 4), ('c5', 8), ('c6', 4), ('g5', 8), ('bb5', -4), ('ab5', 4), ('g5', 8),
('f5*', -4), ('g5', 4), ('d5', 8), ('eb5', -4), ('c5', -4),
('bb*', 8), ('d6', 8), ('c6', 8), ('bb5', 16), ('ab5', 16), ('g5', 16), ('ab5', 16), ('c5', 16), ('d5', 16), ('eb5', -4),
# SONG 3
make_wav(song3, bpm = 132/2, pause = 0., boost = 1.1, fn = "pysynth_chopin.wav")
试了周杰伦的晴天,bmp是67下每分钟:
#!/usr/bin/env python
import pysynth
songx=(('e3',8),('d3',8),('f3',8),('e3',4),('c3',8),('g3',8),('b3',8),('c4',8),('b3',8),('c3',8),('c3',4),('c3',8),('a3',8),('a3',8),
('r',16),('a3',16),('g3',8),('g3',4),('g3',8),('f3',8),('e3',8),('d3',8),('e3',8),('f3',8),('e3',2.25),('e3',8),('f#3',8),('g#3',8),
('e3',4),('f3',8),('g3',8),('b3',8),('d4',8),('b3',8),('c4',8),('c4',6),('c4',16),('c4',8),('g3',8),('g3',8),('a3',8),('g3',8),('f3',8),
('a2',8),('b2',8),('c3',8),('d3',8),('e3',8),('d3',3),('e3',8),('c3',2))
pysynth.make_wav(songx, bpm=67, repeat=0, fn="trymusicx.wav")
再看如何定义这个函数的。首先是模块。
#!/usr/bin/env python
import sys
assert sys.version >= '3.3', "不适用于python2.升级至python3."
from mkfreq import getfreq
pitchhz, keynum = getfreq(pr = True)
出现的pitchhz, keynum = getfreq(pr = True)
找到这个函数,估计是钢琴声音:
pitchhz, keynum = {}, {}
keys_s = ('a', 'a#', 'b', 'c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#')
keys_f = ('a', 'bb', 'b', 'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab')
keys_e = ('a', 'bb', 'cb', 'b#', 'db', 'd', 'eb', 'fb', 'e#', 'gb', 'g', 'ab')
def getfreq(pr = False):
if pr:
print("Piano key frequencies (for equal temperament):")
print("Key number\tScientific name\tFrequency (Hz)")
for k in range(88):
freq = 27.5 * 2.**(k/12.) #表示该音的频率。**是乘方, 27.5hz是钢琴最左边的第一个按键的频率。
oct = (k + 9) // 12 #整除,在音阶中排第几。
note = '%s%u' % (keys_s[k%12], oct) #如果k=2,note='b0';k=30,note='d#3'
if pr:
print("%10u\t%15s\t%14.2f" % (k+1, note.upper(), freq))
pitchhz[note] = freq #k=30,pitchhz[d#3]=27.5*2^(30/12)
keynum[note] = k #k=30,keynum[d#3]=30
return pitchhz, keynum
# construct filnames for Salamander piano samples
sampfn = {}
facs = 1, 2**(1/12), 2**(2/12)
nam = "A", "C", "D#", "F#"
def getfn(layer):
for k in range(88):
oct = (k + 9) // 12
sampfn[k] = "%s%uv%u.wav" % (nam[(k // 3) % 4], oct, layer), facs[k % 3]
return sampfn
继续:
import wave, math, struct
def make_wav(song,bpm=120,transpose=0,pause=.05,boost=1.1,repeat=0,fn="out.wav", silent=False):
f=wave.open(fn,'w')
f.setnchannels(1) #1个轨道
f.setsampwidth(2) #?
f.setframerate(44100) #采样频率:每秒采样个数,它用赫兹(Hz)来表示。
f.setcomptype('NONE','Not Compressed')
bpmfac = 120./bpm #每个节拍的时长,以120为单位。bmp是每分钟多少节拍,晴天是以四分音符为一拍,那每个四分音符的时长就是bpmfac。2s=1个bpmfac.不知道这里为啥要用两倍的。
def length(l):
return 88200./l*bpmfac #相当于44100/(l*(60/bpm)) 即:每秒采集的信号样本个数/((?)*音符长度(s))——每个音符的被采集的信号个数?不知道l是什么
def waves2(hz,l):
a=44100./hz
b=float(l)/44100.*hz
return [a,round(b)]
def sixteenbit(x):
return struct.pack('h', round(32000*x)) #struct.pack是将数据转化成字节流,这里h是hex(Hexadecimal),十六进制
def asin(x):
return math.sin(2.*math.pi*x) #与波形相关,sin(2πx)
def render2(a,b,vol):
b2 = (1.-pause)*b #pause是停顿
l=waves2(a,b2)
ow=b''
q=int(l[0]*l[1])
# 旋律与频率相关:
lf = math.log(a)
lf_fac = (lf-3.) / harm_max
if lf_fac > 1: harm = 0
else: harm = 2. * (1-lf_fac)
decay = 2. / lf
t = (lf-3.) / (8.5-3.)
volfac = 1. + .8 * t * math.cos(math.pi/5.3*(lf-3.))
for x in range(q):
fac=1.
if x<100: fac=x/80.
if 100<=x<300: fac=1.25-(x-100)/800.
if x>q-400: fac=1.-((x-q+400)/400.)
s = float(x)/float(q)
dfac = 1. - s + s * decay
ow=ow+sixteenbit((asin(float(x)/l[0])
+harm*asin(float(x)/(l[0]/2.))
+.5*harm*asin(float(x)/(l[0]/4.)))/4.*fac*vol*dfac*volfac)
fill = max(int(ex_pos - curpos - q), 0)
f.writeframesraw((ow)+(sixteenbit(0)*fill))
return q + fill
# 输出wav格式文件
if silent == False:
print("Writing to file", fn)
curpos = 0
ex_pos = 0.
for rp in range(repeat+1):
for nn, x in enumerate(song): #enumerate是python对于一个可列举的对象,变成索引。nn是序号,x是之前输入的音符,类似于('g5*', 2)这样。假设这个音是('g5*', 2),bpm=60,节奏型4/4
if not nn % 4 and silent == False:
print("[%u/%u]\t" % (nn+1,len(song)))
if x[0]!='r':
if x[0][-1] == '*':
vol = boost
note = x[0][:-1] #如果有音高增强
else:
vol = 1.
note = x[0]
try:
a=pitchhz[note] #a是音符频率值
except:
a=pitchhz[note + '4'] # 若没有则为空拍四分音符。
a = a * 2**transpose #是否转调
if x[1] < 0: #如果节奏型小于零('g5*', 2),bpm=60
b=length(-2.*x[1]/3.) #b=44100/(((-2)*2/3)*(60/bpm))=33075
else:
b=length(x[1]) #否则 b=44100/(2*(60/bpm))=22050
ex_pos = ex_pos + b #ex_pos=22050
curpos = curpos + render2(a,b,vol)
if x[0]=='r':
b=length(x[1])
ex_pos = ex_pos + b
f.writeframesraw(sixteenbit(0)*int(b))
curpos = curpos + int(b)
f.writeframes(b'')
f.close()
print()