import librosa
import matplotlib.patches as ptc
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation as anm
import numpy as np
#-=-=================
def gettimetext(t): # 用于画布显示时间的字符
h=t//3600
m=(t%3600)//60
s=t %60
h=int(h)
m=int(m)
s=round(s,2) # 小数取2位
return '{:0>2d}:{:0>2d}:{:0>5.2f}'.format(h,m,s)
'''
在一个窗口【静态】 展示 n 秒音频的波形,
在窗口【动态】展示 n 秒音频的波形,
是两种不同的模式。
--------------------
动态意味着时空的变化,速度与尺寸的调整。
----------------
运动,有匀速运动,有变速运动 speed
观察,有近距离观察,有远距离观察 zoom
-----------
窗口数据,有两种处理方法。一是用“邻域”的思想,从中间到两边扩展;
另一种是,栈的用法,先进先出。考虑到数据的分散性,这方法的流量不好控制 。
--------------
下面用第一种方法。
'''
def haliluya(ad_file,mod="c",savename="untitle"): # c= see / s=save
# 获取数据
au, sr = librosa.load (ad_file)
#从新采样数据
auu=[]
k=40
for i in range ( 0,len(au),k):
auu.append(au[i])
sr=sr//k
au=auu
#----------------------------------------------------------------
#设定参数
w_width=6 # 窗口尺寸,宽 (刻度个数)
w_height=3
#------
cut_time=10 #音频时间,取段 观察
OAT=round(len(au)/sr,3) #原时长
#investigate
survey_border=cut_time/2 # 秒
#-------------------------------
update_frq=32 # 视觉窗口刷新频率>24每秒
v_d_time_ms=1000/update_frq # 刷新的时间,间隔 ms
v_d_time_sc=1/update_frq # 刷新的时间,间隔 s
#--------------------可以调节-------------------------------------
speed=2 # 播放速度
zoom=2 # 观看细节放大倍数
raise_y=3 # 幅度提升
#--------------------------------------
au=[i*raise_y for i in au] #把 np 数组改成 list,同时放大 y 绝对值
#---------------------------------------------------------------------
mass=(cut_time*sr)*speed/zoom #固定的窗口里,被观察的数据量,将会随着播放速度变大而变大,随着细节放大而减少
windata_bd= int(mass //2 ) # 半个窗口需要的数据 边界 border
PlayTimeAll= len(au)/sr/speed # 音频总时长 (被以某种速度播放时),即动画 videao 时间长
#framesCt= PlayTimeAll*1000 / v_d_time_ms # 计算All可更新次数 =>视频的frames
framesCt=PlayTimeAll * update_frq
framesCt=int(framesCt)+2 #
#----------------------------------------------
'''把某一时刻的 ti 对应数据放在窗口中间观察'''
#------------------------------------------------
def po_ti(ti): #中间 ti 对应的 au 数据位置
return int(ti*sr)
def bo_ti(ti): #中间 ti 对应的 au 数据位置 边界
return po_ti(ti) -windata_bd, po_ti(ti)+windata_bd
def get(a,b): # 取数据,同时填补空白处
_pad=[]; pad_=[];st=b[0];end=b[1]
if st<0 :
if end>0:
_pad=[None]*(-st)
st=0
else:
return [None]*(end-st)
if end>len(a):
pad_=[None]*(end-len(a)) if st<len(a) else [None]*(2*windata_bd)
end=len(a)
return _pad +a[st:end]+ pad_
#-----------------------------------------------------
msg=f'''
采样率: {sr} |
视窗更新频率 :{update_frq}Hz
刷新时间:{v_d_time_ms}ms
总时长:{round(PlayTimeAll,4)}s = {gettimetext(PlayTimeAll)}
原时长:{OAT}
视频总帧数: {round(framesCt,3)}
音频数据量: {len(au)}
一倍速率窗口代表时间: {cut_time} s
播放速度:{speed}x
zoom:{zoom}
'''
print(msg)
#---------------
# 设置画布
fig,ax=plt.subplots( figsize=(w_width,w_height) )
#设置 x 轴刻度,y轴刻度
lim_border= survey_border /zoom # zoom可以改变【数据边界data border】,但改不了数据中心 # 又 mk*sr=windata_bd
lim_bd=lim_border
ax.set_xlim(-lim_bd, lim_bd )
ax.set_ylim(-2, 2)
#plt.axis("off")
#ax.axis("off") # 不显示坐标轴
#设置函数图像(根据 时间(x) <---> 音频数据(y))
line, = ax.plot([],[], lw=0.5,color='blue')
TimeShow=plt.text(3,1.5,'',fontsize=12,color="g") # 显示时间
#设置观察点 sentry post ;observation post;
bw,bh=0.01, 2
sentryPost=ptc.Rectangle((-bw/2,0+bh/2),bw,-bh,edgecolor='#FF0010',facecolor="none",lw=0.5) #中间线
ax.add_patch(sentryPost)
# 定义更新函数 ti
def update( fct ):
if fct==framesCt-1:# 如果 ==ect :好像 quit 不了
quit()
real_world_time= fct * v_d_time_sc #i.e.: video time
time4audioMass= real_world_time * speed
#ax.set_title("video time :"+gettimetext(time ))
TimeShow.set_text(gettimetext(time4audioMass )) # 显示’磁带‘时间
TimeShow.set_x(time4audioMass)
sentryPost.set_x(time4audioMass)
#------更新坐标轴刻度------------
ax.set_xlim(time4audioMass -lim_border, time4audioMass +lim_border ) # 以【数据边界】作为窗口首尾刻度
#------------------------------更新数据----------------------------
c=int(time4audioMass*sr)
b=[c-windata_bd,c+windata_bd]
data= get(au,b)
#-------------------------- 新构 X 部
xzo_twd=np.linspace(time4audioMass-lim_bd,time4audioMass+lim_bd,windata_bd*2)
#--------------------------- 新构 Y 部
line.set_data(xzo_twd, data)
return line, #更新波形图像
#===========
# 创建动画对象
ani_1 = anm(fig, update, frames=framesCt, interval=v_d_time_ms) #ms
# 显示动画
if mod=="s":
ani_1.save(f'{savename}_w.mp4', writer='ffmpeg')
elif mod=="c":
plt.show()
#---------------------------------运用------------------------
svn="te" # 你的 mp3 文件名
haliluya(f"{svn}.mp3","c") # 查看模式
#haliluya(f"{svn}.mp3","s","x2") # 保存模式