piccal/plot_note_analysis.py

334 lines
9.0 KiB
Python

##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org>
##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
import os,sys,pickle,json
import numpy as np
import matplotlib.pyplot as plt
import matplotlib._color_data as mcd
from matplotlib.pyplot import figure
from rms_analysis import rms_analysis_main
from rms_analysis import note_stats
from rms_analysis import key_info_dictionary
from rms_analysis import select_first_stable_note_by_delta_db
from rms_analysis import select_first_stable_note_by_dur
def do_plot(r, statsL ):
fig,ax = plt.subplots()
x = [ i / r.rms_srate for i in range(len(r.tdRmsDbV)) ]
ax.plot( x,r.tdRmsDbV, color="blue" )
x = [ i / r.rms_srate for i in range(len(r.rmsDbV)) ]
ax.plot( x,r.rmsDbV, color="green")
ymx = np.max(r.tdRmsDbV)
ymn = np.min(r.tdRmsDbV)
for r in statsL:
x = r.pkSmpSec
ax.axvline(x,ymax=ymx,ymin=ymn)
ax.text(x,r.pkDb+1,"%i ms" % r.durMs)
ax.text(x,r.pkDb+2,"%4.1f dB" % r.pkDb)
ax.text(x,r.pkDb+3,"%i us" % r.pulse_us)
if hasattr(r,"MIN"):
ax.plot(x,r.pkDb,marker="*",color="red")
plt.show()
def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
sel_note_r = None
for r in statsL:
if r.pkDb > minDb and r.durMs > minDurMs:
sel_note_r = r
break
#print(rr.tdRmsDbV.shape,rr.rmsDbV.shape)
sL = []
if sel_note_r is None:
print("ERROR: No min note found.")
else:
#print(sel_note_r)
bi = max(0, int(round(sel_note_r.pkSmpSec * rr.rms_srate - contextSecs*rr.rms_srate)))
ei = min(rr.tdRmsDbV.shape[0],int(round(sel_note_r.pkSmpSec * rr.rms_srate + contextSecs*rr.rms_srate)))
rr.tdRmsDbV = rr.tdRmsDbV[bi:ei]
rr.rmsDbV = rr.rmsDbV[bi:ei]
offsSec = bi / rr.rms_srate
for r in statsL:
begSmpIdx = int(round(r.begSmpSec * rr.rms_srate))
endSmpIdx = int(round(r.endSmpSec * rr.rms_srate))
if begSmpIdx > bi and endSmpIdx < ei:
r0 = r
r0.begSmpSec = r.begSmpSec - offsSec
r0.endSmpSec = r.endSmpSec - offsSec
r0.pkSmpSec = r.pkSmpSec - offsSec
if r.begSmpSec == sel_note_r.begSmpSec:
setattr(r0,"MIN",True)
sL.append(r0)
return rr,sL
def plot_note_audio_reanalysis( inDir ):
rmsWndMs=300
rmsHopMs=30
dbLinRef=0.001
harmCandN=5
harmN=3
durDecayPct = 50
path = os.path.normpath(inDir)
pathL = inDir.split(os.sep)
take_id = int(pathL[-1])
midi_pitch = int(pathL[-2])
r = rms_analysis_main( inDir, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
r,statsL = select_min_note(r,r.statsL)
do_plot(r,statsL)
def plot_note_audio_reanalysis_dir( inDir, dirL ):
for folder in dirL:
path = os.path.join(inDir,str(folder),"0")
plot_note_audio_reanalysis( path )
def get_all_note_durations( inDir, cacheFn ):
folderL = os.listdir( inDir )
yL = []
for folder in folderL:
takeId = 0
rmsWndMs=300
rmsHopMs=30
dbLinRef=0.001
harmCandN=5
harmN=3
durDecayPct = 40
path = os.path.normpath(inDir)
midi_pitch = int( folder )
takePath = os.path.join(inDir,folder,str(takeId))
if os.path.isfile(os.path.join(takePath,'seq.json')):
print(midi_pitch)
r = rms_analysis_main( takePath, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
xL = []
for i,sr in enumerate(r.statsL):
xL.append((r.pkUsL[i],sr.durMs,sr.pkDb,sr.quality))
yL.append((midi_pitch,xL))
with open(cacheFn,"wb") as f:
pickle.dump(yL,f)
def plot_all_note_durations( cacheFn, pitchL=None, axN=12, yamlCfgFn=None, minDurMs=800, maxPulseUs=None ):
keyMapD = None
if yamlCfgFn is not None:
keyMapD = key_info_dictionary( keyMapL=None, yamlCfgFn=yamlCfgFn)
fig,axL = plt.subplots(axN,1)
fig.set_size_inches(18.5, 10.5*axN)
#cL = list(mcd.CSS4_COLORS.values())
cL = ['black','brown','orangered','saddlebrown','peru','olivedrab','lightgreen','springgreen','cadetblue','slategray','royalblue','navy','darkviolet','deeppink','crimson']
yD=[]
with open(cacheFn,"rb") as f:
yD = dict(pickle.load(f))
cn = 3 #min(1,len(cL)//len(yD))
ci = 0
i = 0
for midi_pitch in pitchL:
if (pitchL is not None and midi_pitch not in pitchL) or midi_pitch not in yD:
continue
xL = yD[midi_pitch]
pkUsL,durMsL,pkDbL,qualityL = tuple(zip(*xL))
if maxPulseUs is not None:
pkUsL = np.array(pkUsL)
pkUsL = pkUsL[ pkUsL < maxPulseUs ]
durMsL = durMsL[0:len(pkUsL)]
pkDbL = pkDbL[0:len(pkUsL)]
qualityL = qualityL[0:len(pkUsL)]
axi = i//(len(pitchL)//axN)
if keyMapD is None:
legendLabel = str(midi_pitch)
else:
legendLabel = getattr(keyMapD[midi_pitch],'type') + " " + getattr(keyMapD[midi_pitch],'class') + str(midi_pitch)
axL[axi].plot(pkUsL,durMsL,color=cL[ci],label=legendLabel)
# plot the quietest stable note
if minDurMs is not None:
sni = select_first_stable_note_by_dur( durMsL, minDurMs )
if sni is not None:
axL[axi].plot(pkUsL[sni],durMsL[sni],marker=".",color='red')
axL[axi].text(pkUsL[sni],durMsL[sni] + 50,"%4.1f" % pkDbL[sni])
sni = select_first_stable_note_by_delta_db( pkDbL, pkUsL )
if sni is not None:
axL[axi].plot(pkUsL[sni],durMsL[sni],marker=".",color='blue')
axL[axi].text(pkUsL[sni],durMsL[sni] + 50,"%4.1f" % pkDbL[sni])
ci += cn
if ci >= len(cL):
ci = ci - len(cL)
axL[axi].legend()
i+=1
plt.show()
def plot_quiet_note_db( cacheFn, yamlCfgFn, minDurMs=700 ):
keyMapD = key_info_dictionary( keyMapL=None, yamlCfgFn=yamlCfgFn)
yD=[]
with open(cacheFn,"rb") as f:
yD = dict(pickle.load(f))
dbL = []
for midi_pitch in range(24,108):
pk0Db = 0
pk1Db = 0
minDb = 0
if midi_pitch in yD:
xL = yD[midi_pitch]
pkUsL,durMsL,pkDbL,qualityL = tuple(zip(*xL))
# plot the quietest stable note
sni = select_first_stable_note_by_dur( durMsL, minDurMs )
if sni is not None:
pk0Db = pkDbL[sni]
sni = select_first_stable_note_by_delta_db( pkDbL, pkUsL )
if sni is not None:
pk1Db = pkDbL[sni]
minDb = min(pk0Db,pk1Db)
dbL.append( (midi_pitch, minDb, pk0Db,pk1Db) )
fig,ax = plt.subplots()
pitchL,minDbL,pk0DbL,pk1DbL = tuple(zip(*dbL))
ax.plot( pitchL, pk0DbL, label="dur" )
ax.plot( pitchL, pk1DbL, label="delta" )
#ax.plot( pitchL, minDbL, label="min" )
for i,pitch in enumerate(pitchL):
ax.text( pitch, pk0DbL[i]+1, "%i %s" % (pitch,getattr(keyMapD[pitch],'type')))
ax.axhline( np.mean(minDbL), label="mean", color="blue" )
ax.axhline( np.median(minDbL), label="median", color="green" )
ax.legend()
plt.show()
def dump_hold_duty_pct( inDir ):
pitchL = []
folderL = os.listdir(inDir)
for folder in folderL:
midi_pitch = int(folder)
fn = os.path.join( inDir,folder,"0","seq.json")
if not os.path.isfile(fn):
print("No sequence file:%s" % (fn))
else:
with open(fn,"r") as f:
d = json.load(f)
if 'holdDutyPct' in d:
holdDutyPctL = [ [0,d['holdDutyPct']] ]
else:
holdDutyPctL = d['holdDutyPctL']
pitchL.append( {'pitch':midi_pitch, 'holdDutyPctL':holdDutyPctL} )
#print(midi_pitch, holdDutyPctL)
pitchL = sorted(pitchL, key=lambda x: x['pitch'])
for d in pitchL:
print("{",d['pitch'],":",d['holdDutyPctL'],"},")
if __name__ == "__main__":
#inDir = sys.argv[1]
#plot_note_analysis( inDir )
pitchL = [ 30,31,32,33,34,35 ]
pitchL = [ 70,71,72,73,74,75 ]
plot_note_audio_reanalysis_dir( "/home/kevin/temp/p_ac_3c",pitchL)
durFn = "/home/kevin/temp/cache_note_dur.pickle"
#get_all_note_durations("/home/kevin/temp/p_ac_3c",durFn)
#plot_all_note_durations(durFn, np.arange(45,55),2,"p_ac.yml",800,20000)
#plot_quiet_note_db(durFn,"p_ac.yml")
#dump_hold_duty_pct( "/home/kevin/temp/p_ac_3c" )