picadae calibration programs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

plot_note_analysis.py 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org>
  2. ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. import os,sys,pickle,json
  4. import numpy as np
  5. import matplotlib.pyplot as plt
  6. import matplotlib._color_data as mcd
  7. from matplotlib.pyplot import figure
  8. from rms_analysis import rms_analysis_main
  9. from rms_analysis import note_stats
  10. from rms_analysis import key_info_dictionary
  11. from rms_analysis import select_first_stable_note_by_delta_db
  12. from rms_analysis import select_first_stable_note_by_dur
  13. def do_plot(r, statsL ):
  14. fig,ax = plt.subplots()
  15. x = [ i / r.rms_srate for i in range(len(r.tdRmsDbV)) ]
  16. ax.plot( x,r.tdRmsDbV, color="blue" )
  17. x = [ i / r.rms_srate for i in range(len(r.rmsDbV)) ]
  18. ax.plot( x,r.rmsDbV, color="green")
  19. ymx = np.max(r.tdRmsDbV)
  20. ymn = np.min(r.tdRmsDbV)
  21. for r in statsL:
  22. x = r.pkSmpSec
  23. ax.axvline(x,ymax=ymx,ymin=ymn)
  24. ax.text(x,r.pkDb+1,"%i ms" % r.durMs)
  25. ax.text(x,r.pkDb+2,"%4.1f dB" % r.pkDb)
  26. ax.text(x,r.pkDb+3,"%i us" % r.pulse_us)
  27. if hasattr(r,"MIN"):
  28. ax.plot(x,r.pkDb,marker="*",color="red")
  29. plt.show()
  30. def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
  31. sel_note_r = None
  32. for r in statsL:
  33. if r.pkDb > minDb and r.durMs > minDurMs:
  34. sel_note_r = r
  35. break
  36. #print(rr.tdRmsDbV.shape,rr.rmsDbV.shape)
  37. sL = []
  38. if sel_note_r is None:
  39. print("ERROR: No min note found.")
  40. else:
  41. #print(sel_note_r)
  42. bi = max(0, int(round(sel_note_r.pkSmpSec * rr.rms_srate - contextSecs*rr.rms_srate)))
  43. ei = min(rr.tdRmsDbV.shape[0],int(round(sel_note_r.pkSmpSec * rr.rms_srate + contextSecs*rr.rms_srate)))
  44. rr.tdRmsDbV = rr.tdRmsDbV[bi:ei]
  45. rr.rmsDbV = rr.rmsDbV[bi:ei]
  46. offsSec = bi / rr.rms_srate
  47. for r in statsL:
  48. begSmpIdx = int(round(r.begSmpSec * rr.rms_srate))
  49. endSmpIdx = int(round(r.endSmpSec * rr.rms_srate))
  50. if begSmpIdx > bi and endSmpIdx < ei:
  51. r0 = r
  52. r0.begSmpSec = r.begSmpSec - offsSec
  53. r0.endSmpSec = r.endSmpSec - offsSec
  54. r0.pkSmpSec = r.pkSmpSec - offsSec
  55. if r.begSmpSec == sel_note_r.begSmpSec:
  56. setattr(r0,"MIN",True)
  57. sL.append(r0)
  58. return rr,sL
  59. def plot_note_audio_reanalysis( inDir ):
  60. rmsWndMs=300
  61. rmsHopMs=30
  62. dbLinRef=0.001
  63. harmCandN=5
  64. harmN=3
  65. durDecayPct = 50
  66. path = os.path.normpath(inDir)
  67. pathL = inDir.split(os.sep)
  68. take_id = int(pathL[-1])
  69. midi_pitch = int(pathL[-2])
  70. r = rms_analysis_main( inDir, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
  71. r,statsL = select_min_note(r,r.statsL)
  72. do_plot(r,statsL)
  73. def plot_note_audio_reanalysis_dir( inDir, dirL ):
  74. for folder in dirL:
  75. path = os.path.join(inDir,str(folder),"0")
  76. plot_note_audio_reanalysis( path )
  77. def get_all_note_durations( inDir, cacheFn ):
  78. folderL = os.listdir( inDir )
  79. yL = []
  80. for folder in folderL:
  81. takeId = 0
  82. rmsWndMs=300
  83. rmsHopMs=30
  84. dbLinRef=0.001
  85. harmCandN=5
  86. harmN=3
  87. durDecayPct = 40
  88. path = os.path.normpath(inDir)
  89. midi_pitch = int( folder )
  90. takePath = os.path.join(inDir,folder,str(takeId))
  91. if os.path.isfile(os.path.join(takePath,'seq.json')):
  92. print(midi_pitch)
  93. r = rms_analysis_main( takePath, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
  94. xL = []
  95. for i,sr in enumerate(r.statsL):
  96. xL.append((r.pkUsL[i],sr.durMs,sr.pkDb,sr.quality))
  97. yL.append((midi_pitch,xL))
  98. with open(cacheFn,"wb") as f:
  99. pickle.dump(yL,f)
  100. def plot_all_note_durations( cacheFn, pitchL=None, axN=12, yamlCfgFn=None, minDurMs=800, maxPulseUs=None ):
  101. keyMapD = None
  102. if yamlCfgFn is not None:
  103. keyMapD = key_info_dictionary( keyMapL=None, yamlCfgFn=yamlCfgFn)
  104. fig,axL = plt.subplots(axN,1)
  105. fig.set_size_inches(18.5, 10.5*axN)
  106. #cL = list(mcd.CSS4_COLORS.values())
  107. cL = ['black','brown','orangered','saddlebrown','peru','olivedrab','lightgreen','springgreen','cadetblue','slategray','royalblue','navy','darkviolet','deeppink','crimson']
  108. yD=[]
  109. with open(cacheFn,"rb") as f:
  110. yD = dict(pickle.load(f))
  111. cn = 3 #min(1,len(cL)//len(yD))
  112. ci = 0
  113. i = 0
  114. for midi_pitch in pitchL:
  115. if (pitchL is not None and midi_pitch not in pitchL) or midi_pitch not in yD:
  116. continue
  117. xL = yD[midi_pitch]
  118. pkUsL,durMsL,pkDbL,qualityL = tuple(zip(*xL))
  119. if maxPulseUs is not None:
  120. pkUsL = np.array(pkUsL)
  121. pkUsL = pkUsL[ pkUsL < maxPulseUs ]
  122. durMsL = durMsL[0:len(pkUsL)]
  123. pkDbL = pkDbL[0:len(pkUsL)]
  124. qualityL = qualityL[0:len(pkUsL)]
  125. axi = i//(len(pitchL)//axN)
  126. if keyMapD is None:
  127. legendLabel = str(midi_pitch)
  128. else:
  129. legendLabel = getattr(keyMapD[midi_pitch],'type') + " " + getattr(keyMapD[midi_pitch],'class') + str(midi_pitch)
  130. axL[axi].plot(pkUsL,durMsL,color=cL[ci],label=legendLabel)
  131. # plot the quietest stable note
  132. if minDurMs is not None:
  133. sni = select_first_stable_note_by_dur( durMsL, minDurMs )
  134. if sni is not None:
  135. axL[axi].plot(pkUsL[sni],durMsL[sni],marker=".",color='red')
  136. axL[axi].text(pkUsL[sni],durMsL[sni] + 50,"%4.1f" % pkDbL[sni])
  137. sni = select_first_stable_note_by_delta_db( pkDbL, pkUsL )
  138. if sni is not None:
  139. axL[axi].plot(pkUsL[sni],durMsL[sni],marker=".",color='blue')
  140. axL[axi].text(pkUsL[sni],durMsL[sni] + 50,"%4.1f" % pkDbL[sni])
  141. ci += cn
  142. if ci >= len(cL):
  143. ci = ci - len(cL)
  144. axL[axi].legend()
  145. i+=1
  146. plt.show()
  147. def plot_quiet_note_db( cacheFn, yamlCfgFn, minDurMs=700 ):
  148. keyMapD = key_info_dictionary( keyMapL=None, yamlCfgFn=yamlCfgFn)
  149. yD=[]
  150. with open(cacheFn,"rb") as f:
  151. yD = dict(pickle.load(f))
  152. dbL = []
  153. for midi_pitch in range(24,108):
  154. pk0Db = 0
  155. pk1Db = 0
  156. minDb = 0
  157. if midi_pitch in yD:
  158. xL = yD[midi_pitch]
  159. pkUsL,durMsL,pkDbL,qualityL = tuple(zip(*xL))
  160. # plot the quietest stable note
  161. sni = select_first_stable_note_by_dur( durMsL, minDurMs )
  162. if sni is not None:
  163. pk0Db = pkDbL[sni]
  164. sni = select_first_stable_note_by_delta_db( pkDbL, pkUsL )
  165. if sni is not None:
  166. pk1Db = pkDbL[sni]
  167. minDb = min(pk0Db,pk1Db)
  168. dbL.append( (midi_pitch, minDb, pk0Db,pk1Db) )
  169. fig,ax = plt.subplots()
  170. pitchL,minDbL,pk0DbL,pk1DbL = tuple(zip(*dbL))
  171. ax.plot( pitchL, pk0DbL, label="dur" )
  172. ax.plot( pitchL, pk1DbL, label="delta" )
  173. #ax.plot( pitchL, minDbL, label="min" )
  174. for i,pitch in enumerate(pitchL):
  175. ax.text( pitch, pk0DbL[i]+1, "%i %s" % (pitch,getattr(keyMapD[pitch],'type')))
  176. ax.axhline( np.mean(minDbL), label="mean", color="blue" )
  177. ax.axhline( np.median(minDbL), label="median", color="green" )
  178. ax.legend()
  179. plt.show()
  180. def dump_hold_duty_pct( inDir ):
  181. pitchL = []
  182. folderL = os.listdir(inDir)
  183. for folder in folderL:
  184. midi_pitch = int(folder)
  185. fn = os.path.join( inDir,folder,"0","seq.json")
  186. if not os.path.isfile(fn):
  187. print("No sequence file:%s" % (fn))
  188. else:
  189. with open(fn,"r") as f:
  190. d = json.load(f)
  191. if 'holdDutyPct' in d:
  192. holdDutyPctL = [ [0,d['holdDutyPct']] ]
  193. else:
  194. holdDutyPctL = d['holdDutyPctL']
  195. pitchL.append( {'pitch':midi_pitch, 'holdDutyPctL':holdDutyPctL} )
  196. #print(midi_pitch, holdDutyPctL)
  197. pitchL = sorted(pitchL, key=lambda x: x['pitch'])
  198. for d in pitchL:
  199. print("{",d['pitch'],":",d['holdDutyPctL'],"},")
  200. if __name__ == "__main__":
  201. #inDir = sys.argv[1]
  202. #plot_note_analysis( inDir )
  203. pitchL = [ 30,31,32,33,34,35 ]
  204. pitchL = [ 70,71,72,73,74,75 ]
  205. plot_note_audio_reanalysis_dir( "/home/kevin/temp/p_ac_3c",pitchL)
  206. durFn = "/home/kevin/temp/cache_note_dur.pickle"
  207. #get_all_note_durations("/home/kevin/temp/p_ac_3c",durFn)
  208. #plot_all_note_durations(durFn, np.arange(45,55),2,"p_ac.yml",800,20000)
  209. #plot_quiet_note_db(durFn,"p_ac.yml")
  210. #dump_hold_duty_pct( "/home/kevin/temp/p_ac_3c" )