Many changes and additions.

This commit is contained in:
kpl 2020-02-29 00:01:58 -05:00
parent ca9580cd50
commit 4d3044af67
13 changed files with 1484 additions and 185 deletions

147
MidiFilePlayer.py Normal file
View File

@ -0,0 +1,147 @@
import json
class MidiFilePlayer:
def __init__( self, cfg, api, midiDev, midiFn, velMapFn="velMapD.json" ):
self.cfg = cfg
self.api = api
self.midiDev = midiDev
self.midiL = [] # [ (us,status,d0,d1) ]
self._parse_midi_file(midiFn)
self.nextIdx = None
self.startMs = 0
self.curDutyPctD = {} # { pitch:duty } track the current hold duty cycle of each note
self.velMapD = {}
self.holdDutyPctD = cfg.calibrateArgs['holdDutyPctD']
with open(velMapFn,'r') as f:
velMapD = json.load(f)
for pitch,usDbL in velMapD.items():
self.velMapD[ int(pitch) ] = usDbL
def start(self, ms):
self.nextIdx = 0
self.startMs = ms
def stop( self, ms):
self.nextIdx = None
for pitch in self.velMapD.keys():
self.api.note_off( int(pitch) )
def tick( self, ms):
if self.nextIdx is None:
return
curOffsMs = ms - self.startMs
while self.nextIdx < len(self.midiL):
if curOffsMs < self.midiL[ self.nextIdx ][0]:
break
cmd = self.midiL[ self.nextIdx ][1]
if cmd == 'non':
self._note_on(self.midiL[ self.nextIdx ][2],self.midiL[ self.nextIdx ][3])
elif cmd == 'nof':
self._note_off(self.midiL[ self.nextIdx ][2])
elif cmd == 'ctl' and self.midiL[ self.nextIdx ][2] == 64:
self.midiDev.send_controller(64,self.midiL[ self.nextIdx ][3])
self.nextIdx += 1
if self.nextIdx >= len(self.midiL):
self.nextIdx = None
def _get_duty_cycle( self, pitch, pulseUsec ):
dutyPct = 50
if pitch in self.holdDutyPctD:
dutyPct = self.holdDutyPctD[pitch][0][1]
for refUsec,refDuty in self.holdDutyPctD[pitch]:
print(pitch,refUsec,refDuty)
if pulseUsec < refUsec:
break
dutyPct = refDuty
return dutyPct
def _set_duty_cycle( self, pitch, pulseUsec ):
dutyPct = self._get_duty_cycle( pitch, pulseUsec )
if pitch not in self.curDutyPctD or self.curDutyPctD[pitch] != dutyPct:
self.curDutyPctD[pitch] = dutyPct
self.api.set_pwm_duty( pitch, dutyPct )
print("Hold Duty Set:",dutyPct)
return dutyPct
def _get_pulse_us( self, pitch, vel ):
usDbL = self.velMapD[pitch]
idx = round(vel * len(usDbL) / 127)
if idx > len(usDbL):
idx = len(usDbL)-1
us = usDbL[ idx ][0]
print('non',pitch,vel,idx,us)
return us
def _note_on( self, pitch, vel ):
if pitch not in self.velMapD:
print("Missing pitch:",pitch)
else:
pulseUs = self._get_pulse_us(pitch,vel)
self._set_duty_cycle( pitch, pulseUs )
self.api.note_on_us( pitch, pulseUs )
def _note_off( self, pitch ):
self.api.note_off( pitch )
def _parse_midi_file( self,fn ):
with open(fn,"r") as f:
for lineNumb,line in enumerate(f):
if lineNumb >= 3:
tokenL = line.split()
if len(tokenL) > 5:
usec = int(tokenL[3])
status = None
d0 = None
d1 = None
if tokenL[5] == 'non' or tokenL[5]=='nof' or tokenL[5]=='ctl':
status = tokenL[5]
d0 = int(tokenL[7])
d1 = int(tokenL[8])
self.midiL.append( (usec/1000,status,d0,d1))
self.midiL = sorted( self.midiL, key=lambda x: x[0] )
if __name__ == "__main__":
midiFn = "/home/kevin/media/audio/midi/txt/988-v25.txt"
mfp = MidiFilePlayer(None,None,midiFn)
print(mfp.midiL[0:10])

View File

@ -1,6 +1,7 @@
import os,types,wave,json,array import os,types,wave,json,array
import numpy as np import numpy as np
from rms_analysis import rms_analyze_one_rt_note from rms_analysis import rms_analyze_one_rt_note
from plot_seq_1 import get_merged_pulse_db_measurements
class Calibrate: class Calibrate:
def __init__( self, cfg, audio, midi, api ): def __init__( self, cfg, audio, midi, api ):
@ -17,6 +18,8 @@ class Calibrate:
self.measD = None # { midi_pitch: [ {pulseUs, db, durMs, targetDb } ] } self.measD = None # { midi_pitch: [ {pulseUs, db, durMs, targetDb } ] }
self.initPulseDbListD = self._get_init_pulseDbD()
self.curNoteStartMs = None self.curNoteStartMs = None
self.curPitchIdx = None self.curPitchIdx = None
self.curTargetDbIdx = None self.curTargetDbIdx = None
@ -47,6 +50,7 @@ class Calibrate:
self.lastAudiblePulseUs = None self.lastAudiblePulseUs = None
self.maxTooShortPulseUs = None self.maxTooShortPulseUs = None
self.pulseDbL = [] self.pulseDbL = []
self.pulseDbL = self.initPulseDbListD[ self.cfg.pitchL[ self.curPitchIdx ] ]
self.deltaUpMult = 1 self.deltaUpMult = 1
self.deltaDnMult = 1 self.deltaDnMult = 1
self.curTargetDbIdx = -1 self.curTargetDbIdx = -1
@ -84,7 +88,7 @@ class Calibrate:
self.audio.record_enable(True) self.audio.record_enable(True)
self._do_play_update() self._do_play_only_update()
def tick(self,ms): def tick(self,ms):
@ -105,7 +109,7 @@ class Calibrate:
elif self.state == 'note_off': elif self.state == 'note_off':
if self.playOnlyFl: if self.playOnlyFl:
if not self._do_play_update(): if not self._do_play_only_update():
self.stop(ms) self.stop(ms)
self.state = 'stopped' self.state = 'stopped'
else: else:
@ -120,7 +124,7 @@ class Calibrate:
self.state = 'started' self.state = 'started'
def _calc_play_pulse_us( self, pitch, targetDb ): def _calc_play_only_pulse_us( self, pitch, targetDb ):
pulseDbL = [] pulseDbL = []
for d in self.measD[ pitch ]: for d in self.measD[ pitch ]:
@ -136,7 +140,7 @@ class Calibrate:
return np.mean(pulseL) return np.mean(pulseL)
def _do_play_update( self ): def _do_play_only_update( self ):
if self.curPitchIdx >= 0: if self.curPitchIdx >= 0:
self._meas_note( self.cfg.pitchL[self.curPitchIdx], self.curPulseUs ) self._meas_note( self.cfg.pitchL[self.curPitchIdx], self.curPulseUs )
@ -150,7 +154,7 @@ class Calibrate:
pitch = self.cfg.pitchL[ self.curPitchIdx ] pitch = self.cfg.pitchL[ self.curPitchIdx ]
targetDb = self.cfg.targetDbL[ self.curTargetDbIdx ] targetDb = self.cfg.targetDbL[ self.curTargetDbIdx ]
self.curPulseUs = self._calc_play_pulse_us( pitch, targetDb ) self.curPulseUs = self._calc_play_only_pulse_us( pitch, targetDb )
self.curTargetDb = targetDb self.curTargetDb = targetDb
if self.curPulseUs == -1: if self.curPulseUs == -1:
@ -161,7 +165,24 @@ class Calibrate:
return True return True
def _get_init_pulseDbD( self ):
initPulseDbListD = {}
print("Calculating initial calibration search us/db lists ...")
if self.cfg.inDir is not None:
for pitch in self.cfg.pitchL:
print(pitch)
inDir = os.path.expanduser( self.cfg.inDir )
usL,dbL,_,_,_ = get_merged_pulse_db_measurements( inDir, pitch, self.cfg.analysisD )
initPulseDbListD[pitch] = [ (us,db) for us,db in zip(usL,dbL) ]
return initPulseDbListD
def _get_duty_cycle( self, pitch, pulseUsec ): def _get_duty_cycle( self, pitch, pulseUsec ):
@ -232,8 +253,9 @@ class Calibrate:
return int(round(curPulse + np.sign(targetDb - curDb) * delta_pulse)) return int(round(curPulse + np.sign(targetDb - curDb) * delta_pulse))
def _step( self, targetDb, dbL, pulseL ): def _step( self, targetDb ):
# get the last two pulse/db samples
pulse0,db0 = self.pulseDbL[-2] pulse0,db0 = self.pulseDbL[-2]
pulse1,db1 = self.pulseDbL[-1] pulse1,db1 = self.pulseDbL[-1]
@ -255,19 +277,22 @@ class Calibrate:
def _calc_next_pulse_us( self, targetDb ): def _calc_next_pulse_us( self, targetDb ):
# sort pulseDb ascending on db # sort pulseDb ascending on db
#self.pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] ) pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
# get the set of us/db values tried so far
pulseL,dbL = zip(*self.pulseDbL) pulseL,dbL = zip(*pulseDbL)
max_i = np.argmax(dbL) max_i = np.argmax(dbL)
min_i = np.argmin(dbL) min_i = np.argmin(dbL)
# if the targetDb is greater than the max. db value achieved so far
if targetDb > dbL[max_i]: if targetDb > dbL[max_i]:
pu = pulseL[max_i] + self.deltaUpMult * 500 pu = pulseL[max_i] + self.deltaUpMult * 500
self.deltaUpMult += 1 self.deltaUpMult += 1
# if the targetDb is less than the min. db value achieved so far
elif targetDb < dbL[min_i]: elif targetDb < dbL[min_i]:
pu = pulseL[min_i] - self.deltaDnMult * 500 pu = pulseL[min_i] - self.deltaDnMult * 500
self.deltaDnMult += 1 self.deltaDnMult += 1
@ -277,12 +302,18 @@ class Calibrate:
pu = self.maxTooShortPulseUs + (abs(pulseL[min_i] - self.maxTooShortPulseUs))/2 pu = self.maxTooShortPulseUs + (abs(pulseL[min_i] - self.maxTooShortPulseUs))/2
self.deltaDnMult = 1 self.deltaDnMult = 1
else: else:
# the targetDb value is inside the min/max range of the db values acheived so far
self.deltaUpMult = 1 self.deltaUpMult = 1
self.deltaDnMult = 1 self.deltaDnMult = 1
# interpolate the new pulse value based on the values seen so far
# TODO: use only closest 5 values rather than all values
pu = np.interp([targetDb],dbL,pulseL) pu = np.interp([targetDb],dbL,pulseL)
# the selected pulse has already been sampled
if int(pu) in pulseL: if int(pu) in pulseL:
pu = self._step(targetDb, dbL, pulseL ) pu = self._step(targetDb )
return max(min(pu,self.cfg.maxPulseUs),self.cfg.minPulseUs) return max(min(pu,self.cfg.maxPulseUs),self.cfg.minPulseUs)
@ -319,23 +350,23 @@ class Calibrate:
else: else:
# this is a valid measurement store it to the pulse-db table # this is a valid measurement, store it to the pulse-db table
self.pulseDbL.append( (self.curPulseUs,db) ) self.pulseDbL.append( (self.curPulseUs,db) )
# track the most recent audible note - to return to if a successive note is too short # track the most recent audible note (to return to if a successive note is too short)
self.lastAudiblePulseUs = self.curPulseUs self.lastAudiblePulseUs = self.curPulseUs
# calc the upper and lower bounds db range # calc the upper and lower bounds db range
lwr_db = self.curTargetDb * ((100.0 - self.cfg.tolDbPct)/100.0) lwr_db = self.curTargetDb * ((100.0 - self.cfg.tolDbPct)/100.0)
upr_db = self.curTargetDb * ((100.0 + self.cfg.tolDbPct)/100.0) upr_db = self.curTargetDb * ((100.0 + self.cfg.tolDbPct)/100.0)
# was this note is inside the db range then set the 'match' flag # if this note was inside the db range then set the 'match' flag
if lwr_db <= db and db <= upr_db: if lwr_db <= db and db <= upr_db:
self.curMatchN += 1 self.curMatchN += 1
measD['matchFl'] = True measD['matchFl'] = True
print("MATCH!") print("MATCH!")
# # calculate the next pulse length
self.curPulseUs = int(self._calc_next_pulse_us(self.curTargetDb)) self.curPulseUs = int(self._calc_next_pulse_us(self.curTargetDb))
# if at least minMatchN matches have been made on this pitch/targetDb # if at least minMatchN matches have been made on this pitch/targetDb
@ -371,8 +402,6 @@ class Calibrate:
sigV = buf_result.value sigV = buf_result.value
# get the annotated begin and end of the note as sample indexes into sigV # get the annotated begin and end of the note as sample indexes into sigV
bi = int(round(annD['beg_ms'] * self.audio.srate / 1000)) bi = int(round(annD['beg_ms'] * self.audio.srate / 1000))
ei = int(round(annD['end_ms'] * self.audio.srate / 1000)) ei = int(round(annD['end_ms'] * self.audio.srate / 1000))
@ -384,7 +413,6 @@ class Calibrate:
bi = max(0,bi - noteOffSmp_o_2) bi = max(0,bi - noteOffSmp_o_2)
ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1) ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1)
ar = types.SimpleNamespace(**self.cfg.analysisD) ar = types.SimpleNamespace(**self.cfg.analysisD)
# shift the annotatd begin/end of the note to be relative to index bi # shift the annotatd begin/end of the note to be relative to index bi
@ -394,8 +422,9 @@ class Calibrate:
#print("MEAS:",begMs,endMs,bi,ei,sigV.shape,self.audio.is_recording_enabled(),ar) #print("MEAS:",begMs,endMs,bi,ei,sigV.shape,self.audio.is_recording_enabled(),ar)
# analyze the note # analyze the note
resD = rms_analyze_one_rt_note( sigV[bi:ei], self.audio.srate, begMs, endMs, midi_pitch, rmsWndMs=ar.rmsWndMs, rmsHopMs=ar.rmsHopMs, dbRefWndMs=ar.dbRefWndMs, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct ) resD = rms_analyze_one_rt_note( sigV[bi:ei], self.audio.srate, begMs, endMs, midi_pitch, rmsWndMs=ar.rmsWndMs, rmsHopMs=ar.rmsHopMs, dbLinRef=ar.dbLinRef, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct )
resD["pulse_us"] = pulse_us resD["pulse_us"] = pulse_us
resD["midi_pitch"] = midi_pitch resD["midi_pitch"] = midi_pitch
@ -408,7 +437,6 @@ class Calibrate:
print( "%4.1f hm:%4.1f (%4.1f) %4i td:%4.1f (%4.1f) %4i" % (self.curTargetDb,resD['hm']['db'], resD['hm']['db']-self.curTargetDb, resD['hm']['durMs'], resD['td']['db'], resD['td']['db']-self.curTargetDb, resD['td']['durMs'])) print( "%4.1f hm:%4.1f (%4.1f) %4i td:%4.1f (%4.1f) %4i" % (self.curTargetDb,resD['hm']['db'], resD['hm']['db']-self.curTargetDb, resD['hm']['durMs'], resD['td']['db'], resD['td']['db']-self.curTargetDb, resD['td']['durMs']))
return resD return resD
@ -428,12 +456,14 @@ class Calibrate:
return False return False
# reset the variables prior to begining the next target search
self.curTargetDb = self.cfg.targetDbL[ self.curTargetDbIdx ] self.curTargetDb = self.cfg.targetDbL[ self.curTargetDbIdx ]
self.curMatchN = 0 self.curMatchN = 0
self.curAttemptN = 0 self.curAttemptN = 0
self.lastAudiblePulseUs = None self.lastAudiblePulseUs = None
self.maxTooShortPulseUs = None self.maxTooShortPulseUs = None
self.pulseDbL = [] self.pulseDbL = []
self.pulseDbL = self.initPulseDbListD[ self.cfg.pitchL[ self.curPitchIdx ] ]
self.deltaUpMult = 1 self.deltaUpMult = 1
self.deltaDnMult = 1 self.deltaDnMult = 1
return True return True

45
calibrate_plot.py Normal file
View File

@ -0,0 +1,45 @@
import os,sys,json
import common
import matplotlib.pyplot as plt
import plot_seq_1
def plot_calibrate( cfg, pitch, dataD ):
dataL = [ (d['pulse_us'], d['hm']['db'], d['targetDb'], d['matchFl'],d['skipMeasFl'],d['annIdx']) for d in dataD['measD'][pitch] ]
udmL = [(t[0],t[1],t[3]) for t in dataL]
udmL = sorted( udmL, key=lambda x: x[0] )
usL,dbL,matchL = zip(*udmL)
fig,ax = plt.subplots()
musL = [us for us,db,m in udmL if m]
mdbL = [db for us,db,m in udmL if m]
ax.plot(musL,mdbL,marker='o',color='red',linestyle='None')
ax.plot(usL,dbL,marker='.')
initDataDir = os.path.expanduser(dataD['cfg']['inDir'])
usL,dbL,_,_,_ = plot_seq_1.get_merged_pulse_db_measurements( initDataDir, int(pitch), cfg['analysisD'] )
ax.plot(usL,dbL,marker='.')
plt.show()
if __name__ == "__main__":
inDir = sys.argv[1]
cfgFn = sys.argv[2]
pitch = sys.argv[3]
cfg = common.parse_yaml_cfg(cfgFn)
cfg = cfg.calibrateArgs
dataFn = os.path.join(inDir,"meas.json")
with open(dataFn,"r") as f:
dataD = json.load(f)
print("pitchL:",dataD['cfg']['pitchL'],"targetDbL:",dataD['cfg']['targetDbL'])
plot_calibrate( cfg, pitch, dataD )

85
elbow.py Normal file
View File

@ -0,0 +1,85 @@
import sys,os
import numpy as np
import common
import rms_analysis
def fit_points_to_reference( usL, dbL, usRefL, dbRefL ):
dbV = None
yyL = [ (db,dbRefL[ usRefL.index(us)]) for i,(us,db) in enumerate(zip(usL,dbL)) if us in usRefL ]
if len(yyL) < 10:
print("NO FIT")
else:
y0L,yrL = zip(*yyL)
yN = len(y0L)
A = np.vstack([np.ones(yN),y0L]).T
c,m = np.linalg.lstsq(A,yrL,rcond=None)[0]
dbV = (np.array(dbL) * m) + c
return dbV
def find_elbow( usL, dbL, pointsPerLine=10 ):
ppl_2 = int(pointsPerLine/2)
dL = []
i = pointsPerLine
# for each consecutive set of 'pointsPerLine' points in usL and dbL
while i < len(usL):
# find the x,y coordinates of the first 'ppl_2' coordinates
x0L = np.array([ (us,1.0) for us in usL[i-pointsPerLine:i-ppl_2] ])
y0L = np.array(usL[i-pointsPerLine:i-ppl_2])
# find the x,y coordinates of the second 'ppl_2' coordinates
x1L = np.array([ (us,1.0) for us in usL[i-ppl_2:i]])
y1L = np.array(dbL[i-ppl_2:i])
m0,c0 = np.linalg.lstsq(x0L,y0L,rcond=None)[0] # fit a line through the first set of points
m1,c1 = np.linalg.lstsq(x1L,y1L,rcond=None)[0] # fit a line through the second set of points
# store the angle between the two lines
dL.append(m1-m0)
i += 1
# find the max angle
i = np.argmax( dL )
# return the x,y coordinate of the first data point of the second line
return (usL[i+ppl_2],dbL[i+ppl_2])
def find_elbow_main( cfg, inDir, midi_pitch, takeId ):
inDir = os.path.join(inDir,str(pitch),str(takeId))
analysisArgsD = cfg.analysisArgs['rmsAnalysArgs']
r = rms_analysis_main( inDir, int(midi_pitch), **analysisD )
usL = r.pkUsL
dbL = r.pkDbL
return find_elbow(r.pkUsL,r.pkDbL)
if __name__ == "__main__":
inDir = sys.argv[1]
cfgFn = sys.argv[2]
pitch = sys.argv[3]
cfg = common.parse_yaml_cfg(cfgFn)
find_elbow( cfg, inDir, pitch, 0 )

90
p_ac.py
View File

@ -10,11 +10,13 @@ from MidiDevice import MidiDevice
from result import Result from result import Result
from common import parse_yaml_cfg from common import parse_yaml_cfg
from plot_seq import form_resample_pulse_time_list from plot_seq import form_resample_pulse_time_list
from plot_seq import get_resample_points_wrap from plot_seq_1 import get_resample_points_wrap
from plot_seq import form_final_pulse_list from plot_seq import form_final_pulse_list
from rt_note_analysis import RT_Analyzer from rt_note_analysis import RT_Analyzer
from keyboard import Keyboard from keyboard import Keyboard
from calibrate import Calibrate from calibrate import Calibrate
from rms_analysis import rms_analyze_one_rt_note_wrap
from MidiFilePlayer import MidiFilePlayer
class AttackPulseSeq: class AttackPulseSeq:
""" Sequence a fixed pitch over a list of attack pulse lengths.""" """ Sequence a fixed pitch over a list of attack pulse lengths."""
@ -29,7 +31,8 @@ class AttackPulseSeq:
self.noteDurMs = noteDurMs # duration of each chord in milliseconds self.noteDurMs = noteDurMs # duration of each chord in milliseconds
self.pauseDurMs = pauseDurMs # duration between end of previous note and start of next self.pauseDurMs = pauseDurMs # duration between end of previous note and start of next
self.holdDutyPctL= None # hold voltage duty cycle table [ (minPulseSeqUsec,dutyCyclePct) ] self.holdDutyPctL= None # hold voltage duty cycle table [ (minPulseSeqUsec,dutyCyclePct) ]
self.holdDutyPctD= None # { us:dutyPct } for each us in self.pulseUsL
self.silentNoteN = None
self.pulse_idx = 0 # Index of next pulse self.pulse_idx = 0 # Index of next pulse
self.state = None # 'note_on','note_off' self.state = None # 'note_on','note_off'
self.prevHoldDutyPct = None self.prevHoldDutyPct = None
@ -39,11 +42,13 @@ class AttackPulseSeq:
self.playOnlyFl = False self.playOnlyFl = False
self.rtAnalyzer = RT_Analyzer() self.rtAnalyzer = RT_Analyzer()
def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, playOnlyFl=False ): def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, holdDutyPctD, playOnlyFl=False ):
self.outDir = outDir # directory to write audio file and results self.outDir = outDir # directory to write audio file and results
self.pitch = pitch # note to play self.pitch = pitch # note to play
self.pulseUsL = pulseUsL # one onset pulse length in microseconds per sequence element self.pulseUsL = pulseUsL # one onset pulse length in microseconds per sequence element
self.holdDutyPctL = holdDutyPctL self.holdDutyPctL = holdDutyPctL
self.holdDutyPctD = holdDutyPctD
self.silentNoteN = 0
self.pulse_idx = 0 self.pulse_idx = 0
self.state = 'note_on' self.state = 'note_on'
self.prevHoldDutyPct = None self.prevHoldDutyPct = None
@ -84,17 +89,39 @@ class AttackPulseSeq:
# if waiting to turn a note off # if waiting to turn a note off
elif self.state == 'note_off': elif self.state == 'note_off':
self._note_off(ms) self._note_off(ms)
self._count_silent_notes()
self.pulse_idx += 1 self.pulse_idx += 1
# if all notes have been played # if all notes have been played
if self.pulse_idx >= len(self.pulseUsL): if self.pulse_idx >= len(self.pulseUsL): # or self.silentNoteN >= self.cfg.maxSilentNoteCount:
self.stop(ms) self.stop(ms)
else: else:
assert(0) assert(0)
def _count_silent_notes( self ):
annBegMs = self.eventTimeL[ self.pulse_idx ][0]
annEndMs = self.eventTimeL[ self.pulse_idx ][1]
minDurMs = self.cfg.silentNoteMinDurMs
maxPulseUs = self.cfg.silentNoteMaxPulseUs
resD = rms_analyze_one_rt_note_wrap( self.audio, annBegMs, annEndMs, self.pitch, self.pauseDurMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
print( " %4.1f db %4i ms %i" % (resD['hm']['db'], resD['hm']['durMs'], self.pulse_idx))
if resD is not None and resD['hm']['durMs'] < minDurMs and self.pulseUsL[ self.pulse_idx ] < maxPulseUs:
self.silentNoteN += 1
print("SILENT", self.silentNoteN)
else:
self.silentNoteN = 0
return self.silentNoteN
def _get_duty_cycle( self, pulseUsec ): def _get_duty_cycle( self, pulseUsec ):
return self.holdDutyPctD[ pulseUsec ]
dutyPct = self.holdDutyPctL[0][1] dutyPct = self.holdDutyPctL[0][1]
for refUsec,refDuty in self.holdDutyPctL: for refUsec,refDuty in self.holdDutyPctL:
if pulseUsec < refUsec: if pulseUsec < refUsec:
@ -232,7 +259,7 @@ class CalibrateKeys:
print(outDir_id,outDir) print(outDir_id,outDir)
# if this is not the first time this note has been sampled then get the resample locations # if this is not the first time this note has been sampled then get the resample locations
if outDir_id == 0: if (outDir_id == 0) or self.cfg.useFullPulseListFl:
self.pulseUsL = self.cfg.full_pulseL self.pulseUsL = self.cfg.full_pulseL
else: else:
#self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs ) #self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs )
@ -252,8 +279,23 @@ class CalibrateKeys:
if not os.path.isdir(outDir): if not os.path.isdir(outDir):
os.mkdir(outDir) os.mkdir(outDir)
#------------------------
j = 0
holdDutyPctD = {}
for us in self.pulseUsL:
if j+1<len(holdDutyPctL) and us >= holdDutyPctL[j+1][0]:
j += 1
holdDutyPctD[ us ] = holdDutyPctL[j][1]
#------------------------
if self.cfg.reversePulseListFl:
self.pulseUsL = [ us for us in reversed(self.pulseUsL) ]
# start the sequencer # start the sequencer
self.seq.start( ms, outDir, pitch, self.pulseUsL, holdDutyPctL, playOnlyFl ) self.seq.start( ms, outDir, pitch, self.pulseUsL, holdDutyPctL, holdDutyPctD, playOnlyFl )
def _calc_next_out_dir_id( self, outDir ): def _calc_next_out_dir_id( self, outDir ):
@ -274,6 +316,7 @@ class App:
self.cal_keys = None self.cal_keys = None
self.keyboard = None self.keyboard = None
self.calibrate = None self.calibrate = None
self.midiFilePlayer = None
def setup( self, cfg ): def setup( self, cfg ):
self.cfg = cfg self.cfg = cfg
@ -281,17 +324,22 @@ class App:
self.audioDev = AudioDevice() self.audioDev = AudioDevice()
self.midiDev = MidiDevice() self.midiDev = MidiDevice()
res = None
# #
# TODO: unify the result error handling # TODO: unify the result error handling
# (the API and the audio device return two diferent 'Result' types # (the API and the audio device return two diferent 'Result' types
# #
if hasattr(cfg,'audio'):
res = self.audioDev.setup(**cfg.audio) res = self.audioDev.setup(**cfg.audio)
if not res: if not res:
self.audio_dev_list(0) self.audio_dev_list(0)
else:
else:
self.audioDev = None
if True:
if hasattr(cfg,'midi'): if hasattr(cfg,'midi'):
res = self.midiDev.setup(**cfg.midi) res = self.midiDev.setup(**cfg.midi)
@ -315,12 +363,15 @@ class App:
self.keyboard = Keyboard( cfg, self.audioDev, self.api ) self.keyboard = Keyboard( cfg, self.audioDev, self.api )
self.calibrate = Calibrate( cfg.calibrateArgs, self.audioDev, self.midiDev, self.api ) self.calibrate = None #Calibrate( cfg.calibrateArgs, self.audioDev, self.midiDev, self.api )
self.midiFilePlayer = MidiFilePlayer( cfg, self.api, self.midiDev, cfg.midiFileFn )
return res return res
def tick( self, ms ): def tick( self, ms ):
if self.audioDev is not None:
self.audioDev.tick(ms) self.audioDev.tick(ms)
if self.cal_keys: if self.cal_keys:
@ -332,6 +383,9 @@ class App:
if self.calibrate: if self.calibrate:
self.calibrate.tick(ms) self.calibrate.tick(ms)
if self.midiFilePlayer:
self.midiFilePlayer.tick(ms)
def audio_dev_list( self, ms ): def audio_dev_list( self, ms ):
portL = self.audioDev.get_port_list( True ) portL = self.audioDev.get_port_list( True )
@ -383,6 +437,20 @@ class App:
self.keyboard.stop(ms) self.keyboard.stop(ms)
self.calibrate.stop(ms) self.calibrate.stop(ms)
def midi_file_player_start( self, ms ):
self.midiFilePlayer.start(ms)
def midi_file_player_stop( self, ms ):
self.midiFilePlayer.stop(ms)
def pedal_down( self, ms ):
print("pedal_down")
self.midiDev.send_controller(64, 100 )
def pedal_up( self, ms ):
print("pedal_up");
self.midiDev.send_controller(64, 0 )
def quit( self, ms ): def quit( self, ms ):
if self.api: if self.api:
self.api.close() self.api.close()
@ -513,6 +581,10 @@ class Shell:
'r':{ "func":"keyboard_repeat_pulse_idx", "minN":1, "maxN":1, "help":"Repeat pulse index across keyboard with new pulse_idx"}, 'r':{ "func":"keyboard_repeat_pulse_idx", "minN":1, "maxN":1, "help":"Repeat pulse index across keyboard with new pulse_idx"},
'K':{ "func":"keyboard_start_target_db", "minN":3, "maxN":3, "help":"Play db across keyboard"}, 'K':{ "func":"keyboard_start_target_db", "minN":3, "maxN":3, "help":"Play db across keyboard"},
'R':{ "func":"keyboard_repeat_target_db", "minN":1, "maxN":1, "help":"Repeat db across keyboard with new pulse_idx"}, 'R':{ "func":"keyboard_repeat_target_db", "minN":1, "maxN":1, "help":"Repeat db across keyboard with new pulse_idx"},
'F':{ "func":"midi_file_player_start", "minN":0, "maxN":0, "help":"Play the MIDI file."},
'f':{ "func":"midi_file_player_stop", "minN":0, "maxN":0, "help":"Stop the MIDI file."},
'P':{ "func":"pedal_down", "minN":0, "maxN":0, "help":"Pedal down."},
'U':{ "func":"pedal_up", "minN":0, "maxN":0, "help":"Pedal up."},
} }
def _help( self, _=None ): def _help( self, _=None ):

151
p_ac.yml
View File

@ -3,19 +3,19 @@
# Audio device setup # Audio device setup
audio: { audio_off: {
inPortLabel: "5 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device", inPortLabel: "5 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
outPortLabel: , outPortLabel: ,
}, },
midi_off: { midi: {
inMonitorFl: False, inMonitorFl: False,
outMonitorFl: False, outMonitorFl: False,
throughFl: False, throughFl: False,
inPortLabel: "Fastlane:Fastlane MIDI A", #inPortLabel: "Fastlane:Fastlane MIDI A",
outPortLabel: "Fastlane:Fastlane MIDI A" #outPortLabel: "Fastlane:Fastlane MIDI A"
#inPortLabel: "picadae:picadae MIDI 1", inPortLabel: "picadae:picadae MIDI 1",
#outPortLabel: "picadae:picadae MIDI 1" outPortLabel: "picadae:picadae MIDI 1"
}, },
# Picadae API args # Picadae API args
@ -27,10 +27,19 @@
# MeasureSeq args # MeasureSeq args
outDir: "~/temp/p_ac_3e", outDir: "~/temp/p_ac_3g",
noteDurMs: 1000, noteDurMs: 500,
pauseDurMs: 1000, pauseDurMs: 500,
#holdDutyPctL: [ [0,50], [22000,55] ], reversePulseListFl: True,
useFullPulseListFl: True,
maxSilentNoteCount: 4,
silentNoteMaxPulseUs: 15000,
silentNoteMinDurMs: 250,
# Midi file player
midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt",
full_pulse0L: [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 8000, 9000, 10000, 12000, 14000, 18000, 22000, 26000, 30000, 34000, 40000], full_pulse0L: [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 8000, 9000, 10000, 12000, 14000, 18000, 22000, 26000, 30000, 34000, 40000],
full_pulse1L: [ 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 20000, 22000, 24000, 26000, 30000, 32000, 34000, 36000, 40000], full_pulse1L: [ 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 20000, 22000, 24000, 26000, 30000, 32000, 34000, 36000, 40000],
@ -44,11 +53,18 @@
full_pulse6L: [ 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ], full_pulse6L: [ 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
full_pulseL: [ 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ], full_pulseMainL: [ 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
full_pulse7L: [ 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ], full_pulse8L: [ 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
full_pulse9L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
full_pulseL: [11000, 11075, 11150, 11225, 11300, 11375, 11450, 11525, 11600,11675, 11750, 11825, 11900, 11975, 12050, 12125, 12200, 12275,12350, 12425, 12500, 12575, 12650, 12725, 12800, 12875, 12950, 13025, 13100, 13175, 13250, 13325, 13400, 13475, 13550, 13625, 13700, 13775, 13850, 13925, 14000, 14075, 14150, 14225, 14300, 14375, 14450, 14525, 14600, 14675, 14750, 14825, 14900, 14975],
full_pulse10L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
full_pulse11L: [ 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000 ],
full_pulse12L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000 ],
# RMS analysis args # RMS analysis args
analysisArgs: { analysisArgs: {
@ -61,8 +77,8 @@
durDecayPct: 40, # percent drop in RMS to indicate the end of a note durDecayPct: 40, # percent drop in RMS to indicate the end of a note
}, },
resampleMinDb: 10.0, # note's less than this will be skipped resampleMinDb: 7.0, # note's less than this will be skipped
resampleNoiseLimitPct: 1.0, # resampleNoiseLimitPct: 5.0, #
resampleMinDurMs: 800, # notes's whose duration is less than this will be skipped resampleMinDurMs: 800, # notes's whose duration is less than this will be skipped
minAttkDb: 7.0, # threshold of silence level minAttkDb: 7.0, # threshold of silence level
@ -76,11 +92,98 @@
rmsAnalysisCacheFn: "/home/kevin/temp/rms_analysis_cache.pickle" rmsAnalysisCacheFn: "/home/kevin/temp/rms_analysis_cache.pickle"
}, },
manualMinD: {
23: [2, 24],
24: [2, 18],
25: [2, 41],
26: [2, 26], #
27: [2, 35], # (36 is an outlier)
28: [2, 35], # /36 (resample?)
29: [2, 22], # /23 (resample?)
30: [2, 28], # /29
31: [2, 39], #
32: [2, 27], #
33: [2, 10], #
34: [2, 27], # (29 outlier)
35: [2, 15], #
36: [2, 16], # ngz: (0 32 36) (1 31 36)
37: [2, 18], #
38: [2, 33], #
39: [2, 18], #
40: [2, 6], # ngz: (0 25 41)
41: [2, 22], # ngz: (2 9 22)
42: [2, 11], #
43: [2, 7], #(8 outlier)], #
44: [2, 19],
45: [4, 7], # 5 sample traes
46: [2, 4],
47: [2, 11], # /12
48: [2, 27], # /28
49: [2, 12],
50: [2, 6],
51: [2, 14],
52: [2, 26],
53: [3, 24 ], # ngz at onset
54: [2, 21], # /22
55: [2, 10], # /11
56: [2, 5],
57: [2, 6],
58: [2, 11],
59: [2, 5],
60: [2, 13],
61: [4, 5],
62: [2, 7],
63: [2, 12],
64: [3, 33],
65: [2, 23],
66: [2, 36],
67: [2, 16],
68: [2, 1], # needs decreased start us
69: [1, 7],
70: [2, 34],
71: [2, 23],
72: [2, 14],
73: [2, 30],
74: [2, 26],
75: [2, 31],
76: [2, 20],
77: [2, 28],
78: [2, 28],
79: [2, 44],
80: [2, 25],
81: [2, 36],
82: [2, 51], # incorrect hold voltages (resample)
83: [2, 43],
84: [2, 38],
85: [2, 27],
86: [2, 43],
87: [2, 33],
88: [2, 42],
89: [3, 21], # ngz (3 15 19)
91: [2, 4], # bad samples (resample)
92: [2, 10],
93: [2, 42],
94: [2, 39],
95: [2, 19],
96: [2, 1], # needs decreaed start us ngz: (0 22 38)
97: [2, 51],
98: [2, 30],
99: [2, 41],
100: [2, 24],
101: [2, 39],
},
manualAnchorPitchMinDbL: [ 23, 27, 31, 34, 44, 51, 61, 70, 74, 81, 87, 93, 96, 101 ],
manualAnchorPitchMaxDbL: [ 23, 32, 49, 57, 67, 76, 83, 93, 99, 101 ],
calibrateArgs: { calibrateArgs: {
outDir: "~/temp/calib0", outDir: "~/temp/calib2",
outLabel: "test_1", outLabel: "test_3",
inDir: "~/temp/p_ac_3f",
analysisD: { analysisD: {
rmsWndMs: 300, # length of the RMS measurment window rmsWndMs: 300, # length of the RMS measurment window
@ -91,21 +194,23 @@
durDecayPct: 40 # percent drop in RMS to indicate the end of a note durDecayPct: 40 # percent drop in RMS to indicate the end of a note
}, },
noteOnDurMs: 1000, noteOnDurMs: 500,
noteOffDurMs: 1000, noteOffDurMs: 500,
pitchL: [ 44, 45, 46, 47, 48, 49, 50, 51 ], # list of pitches #pitchL: [ 31, 33, 34, 35 ], # list of pitches
targetDbL: [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ], # list of target db #pitchL: [ 80,81,82 ], # 8
pitchL: [ 40,41,42 ], # 12
targetDbL: [ 13 ], # list of target db
minMeasDurMs: 800, # minimum candidate note duration minMeasDurMs: 140, # minimum candidate note duration
tolDbPct: 2.0, # tolerance as a percent of targetDb above/below used to form match db window tolDbPct: 2.0, # tolerance as a percent of targetDb above/below used to form match db window
maxPulseUs: 45000, # max. allowable pulse us maxPulseUs: 45000, # max. allowable pulse us
minPulseUs: 8000, # min. allowable pulse us minPulseUs: 8000, # min. allowable pulse us
initPulseUs: 15000, # pulseUs for first note initPulseUs: 15000, # pulseUs for first note
minMatchN: 3, # at least 3 candidate notes must be within tolDbPct to move on to a new targetDb minMatchN: 3, # at least 3 candidate notes must be within tolDbPct to move on to a new targetDb
maxAttemptN: 30, # give up if more than 20 candidate notes fail for a given targetDb maxAttemptN: 30, # give up if more than 20 candidate notes fail for a given targetDb
dbSrcLabel: 'td', # source of the db measurement 'td' (time-domain) or 'hm' (harmonic) dbSrcLabel: 'hm', # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
holdDutyPctD: { holdDutyPctD: {
23: [[0, 70]], 23: [[0, 70]],

View File

@ -143,7 +143,7 @@ if __name__ == "__main__":
inDir = sys.argv[1] inDir = sys.argv[1]
yamlFn = sys.argv[2] yamlFn = sys.argv[2]
if len(sys.argv) > 3: if len(sys.argv) > 3:
pitch = int(sys.argv[2]) pitch = int(sys.argv[3])
keyInfoD = key_info_dictionary( yamlCfgFn=yamlFn) keyInfoD = key_info_dictionary( yamlCfgFn=yamlFn)
#plot_all_notes( inDir ) #plot_all_notes( inDir )

View File

@ -1,30 +1,61 @@
import os, sys import os, sys,json
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from common import parse_yaml_cfg from common import parse_yaml_cfg
import rms_analysis import rms_analysis
import elbow
def fit_to_reference( pkL, refTakeId ):
us_outL = []
db_outL = []
dur_outL = []
tid_outL = []
dbL,usL,durMsL,takeIdL = tuple(zip(*pkL))
us_refL,db_refL,dur_refL = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==refTakeId])
for takeId in set(takeIdL):
us0L,db0L,dur0L = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==takeId ])
if takeId == refTakeId:
db_outL += db0L
else:
db1V = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
db_outL += db1V.tolist()
us_outL += us0L
dur_outL+= dur0L
tid_outL+= [takeId] * len(us0L)
return zip(db_outL,us_outL,dur_outL,tid_outL)
def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ): def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
inDir = os.path.join(inDir,"%i" % (midi_pitch)) inDir = os.path.join(inDir,"%i" % (midi_pitch))
dirL = os.listdir(inDir) takeDirL = os.listdir(inDir)
pkL = [] pkL = []
# for each take in this directory usRefL = None
for idir in dirL: dbRefL = None
take_number = int(idir) # for each take in this directory
for take_number in range(len(takeDirL)):
# analyze this takes audio and locate the note peaks # analyze this takes audio and locate the note peaks
r = rms_analysis.rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD ) r = rms_analysis.rms_analysis_main( os.path.join(inDir,str(take_number)), midi_pitch, **analysisArgsD )
# store the peaks in pkL[ (db,us) ] # store the peaks in pkL[ (db,us) ]
for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL): for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
pkL.append( (db,us,stats.durMs,take_number) ) pkL.append( (db,us,stats.durMs,take_number) )
pkL = fit_to_reference( pkL, 0 )
# sort the peaks on increasing attack pulse microseconds # sort the peaks on increasing attack pulse microseconds
pkL = sorted( pkL, key= lambda x: x[1] ) pkL = sorted( pkL, key= lambda x: x[1] )
@ -37,21 +68,33 @@ def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
def select_resample_reference_indexes( noiseIdxL ): def select_resample_reference_indexes( noiseIdxL ):
resampleIdxS = set() resampleIdxS = set()
# for each noisy sample index store that index and the index
# before and after it
for i in noiseIdxL: for i in noiseIdxL:
resampleIdxS.add( i ) resampleIdxS.add( i )
if i+1 < len(noiseIdxL):
resampleIdxS.add( i+1 ) resampleIdxS.add( i+1 )
if i-1 >= 0:
resampleIdxS.add( i-1 ) resampleIdxS.add( i-1 )
resampleIdxL = list(resampleIdxS) resampleIdxL = list(resampleIdxS)
# if a single sample point is left out of a region of # if a single sample point is left out of a region of
# contiguous sample points then include this as a resample point # contiguous sample points then include this as a resample point also
for i in resampleIdxL: for i in resampleIdxL:
if i + 1 not in resampleIdxL and i + 2 in resampleIdxL: # BUG BUG BUG: Hardcoded constant if i + 1 not in resampleIdxL and i + 2 in resampleIdxL: # BUG BUG BUG: Hardcoded constant
if i+1 < len(noiseIdxL):
resampleIdxL.append(i+1) resampleIdxL.append(i+1)
return resampleIdxL return resampleIdxL
@ -99,11 +142,13 @@ def locate_resample_regions( usL, dbL, resampleIdxL ):
return reUsL,reDbL return reUsL,reDbL
def get_dur_skip_indexes( durMsL, dbL, takeIdL, minDurMs, minDb ): def get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreL, minDurMs, minDb, noiseLimitPct ):
firstAudibleIdx = None firstAudibleIdx = None
firstNonSkipIdx = None firstNonSkipIdx = None
skipIdxL = [ i for i,(ms,db) in enumerate(zip(durMsL,dbL)) if ms < minDurMs or db < minDb ]
# get the indexes of samples which do not meet the duration, db level, or noise criteria
skipIdxL = [ i for i,(ms,db,score) in enumerate(zip(durMsL,dbL,scoreL)) if ms < minDurMs or db < minDb or score > noiseLimitPct ]
# if a single sample point is left out of a region of # if a single sample point is left out of a region of
# contiguous skipped points then skip this point also # contiguous skipped points then skip this point also
@ -143,18 +188,22 @@ def get_dur_skip_indexes( durMsL, dbL, takeIdL, minDurMs, minDb ):
def get_resample_points( usL, dbL, durMsL, takeIdL, minDurMs, minDb, noiseLimitPct ): def get_resample_points( usL, dbL, durMsL, takeIdL, minDurMs, minDb, noiseLimitPct ):
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, minDurMs, minDb )
durL = [ (usL[i],dbL[i]) for i in skipIdxL ]
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL ) scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )
skipL = [ (usL[i],dbL[i]) for i in skipIdxL ]
noiseIdxL = [ i for i in range(scoreV.shape[0]) if scoreV[i] > noiseLimitPct ] noiseIdxL = [ i for i in range(scoreV.shape[0]) if scoreV[i] > noiseLimitPct ]
noiseL = [ (usL[i],dbL[i]) for i in noiseIdxL ] noiseL = [ (usL[i],dbL[i]) for i in noiseIdxL ]
resampleIdxL = select_resample_reference_indexes( noiseIdxL ) resampleIdxL = select_resample_reference_indexes( noiseIdxL )
if firstNonSkipIdx is not None:
resampleIdxL = [ i for i in resampleIdxL if i >= firstNonSkipIdx ] resampleIdxL = [ i for i in resampleIdxL if i >= firstNonSkipIdx ]
resampleL = [ (usL[i],dbL[i]) for i in resampleIdxL ] resampleL = [ (usL[i],dbL[i]) for i in resampleIdxL ]
reUsL,reDbL = locate_resample_regions( usL, dbL, resampleIdxL ) reUsL,reDbL = locate_resample_regions( usL, dbL, resampleIdxL )
return reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx return reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx
def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ): def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
@ -164,45 +213,74 @@ def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
return reUsL return reUsL
def plot_noise_region( ax, inDir, keyMapD, midi_pitch, analysisArgsD ):
plotResampleFl = False
plotTakesFl = True def plot_us_db_curves( ax, inDir, keyMapD, midi_pitch, analysisArgsD, plotResamplePointsFl=False, plotTakesFl=True, usMax=None ):
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] ) usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
# plot first audible and non-skip position # plot first audible and non-skip position
if False:
if firstNonSkipIdx is not None:
ax.plot( usL[firstNonSkipIdx], dbL[firstNonSkipIdx], markersize=15, marker='+', linestyle='None', color='red') ax.plot( usL[firstNonSkipIdx], dbL[firstNonSkipIdx], markersize=15, marker='+', linestyle='None', color='red')
ax.plot( usL[firstNonSkipIdx], dbL[firstAudibleIdx], markersize=15, marker='*', linestyle='None', color='red')
if firstAudibleIdx is not None:
ax.plot( usL[firstAudibleIdx], dbL[firstAudibleIdx], markersize=15, marker='*', linestyle='None', color='red')
# plot the resample points # plot the resample points
if plotResampleFl: if plotResamplePointsFl:
ax.plot( reUsL, reDbL, markersize=10, marker='x', linestyle='None', color='green') ax.plot( reUsL, reDbL, markersize=13, marker='x', linestyle='None', color='green')
# plot the noisy sample positions # plot the noisy sample positions
if noiseL: if noiseL:
nUsL,nDbL = zip(*noiseL) nUsL,nDbL = zip(*noiseL)
ax.plot( nUsL, nDbL, marker='o', linestyle='None', color='black') ax.plot( nUsL, nDbL, marker='o', markersize=9, linestyle='None', color='black')
# plot the noisy sample positions and the neighbors included in the noisy region # plot the noisy sample positions and the neighbors included in the noisy region
if resampleL: if resampleL:
nUsL,nDbL = zip(*resampleL) nUsL,nDbL = zip(*resampleL)
ax.plot( nUsL, nDbL, marker='*', linestyle='None', color='red') ax.plot( nUsL, nDbL, marker='+', markersize=8, linestyle='None', color='red')
# plot actual sample points # plot actual sample points
elbow_us = None
elbow_db = None
elbow_len = None
usL,dbL,takeIdL = zip(*[(us,dbL[i],takeIdL[i]) for i,us in enumerate(usL) if usMax is None or us <= usMax])
if plotTakesFl: if plotTakesFl:
for takeId in list(set(takeIdL)): for takeId in list(set(takeIdL)):
# get the us,db points included in this take
xL,yL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId ]) xL,yL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId ])
ax.plot(xL,yL, marker='.')
ax.plot(xL,yL, marker='.',label=takeId)
for i,(x,y) in enumerate(zip(xL,yL)): for i,(x,y) in enumerate(zip(xL,yL)):
ax.text(x,y,str(i)) ax.text(x,y,str(i))
#if elbow_len is None or len(xL) > elbow_len:
if takeId+1 == len(set(takeIdL)):
elbow_us,elbow_db = elbow.find_elbow(xL,yL)
elbow_len = len(xL)
else: else:
ax.plot(usL, dbL, marker='.') ax.plot(usL, dbL, marker='.')
# plot the duration skip points ax.plot([elbow_us],[elbow_db],marker='*',markersize=12,color='red',linestyle='None')
if durL:
nUsL,nDbL = zip(*durL) # plot the skip points in yellow
if False:
if skipL:
nUsL,nDbL = zip(*skipL)
ax.plot( nUsL, nDbL, marker='.', linestyle='None', color='yellow') ax.plot( nUsL, nDbL, marker='.', linestyle='None', color='yellow')
# plot the locations where the hold duty cycle changes with vertical black lines # plot the locations where the hold duty cycle changes with vertical black lines
@ -214,35 +292,503 @@ def plot_noise_region( ax, inDir, keyMapD, midi_pitch, analysisArgsD ):
# plot the 'minDb' reference line # plot the 'minDb' reference line
ax.axhline(analysisArgsD['resampleMinDb'] ,color='black') ax.axhline(analysisArgsD['resampleMinDb'] ,color='black')
if os.path.isfile("minInterpDb.json"):
with open("minInterpDb.json","r") as f:
r = json.load(f)
if midi_pitch in r['pitchL']:
ax.axhline( r['minDbL'][ r['pitchL'].index(midi_pitch) ], color='blue' )
ax.axhline( r['maxDbL'][ r['pitchL'].index(midi_pitch) ], color='blue' )
ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class'])) ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
def plot_noise_regions_main( inDir, cfg, pitchL ): def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None ):
analysisArgsD = cfg.analysisArgs analysisArgsD = cfg.analysisArgs
keyMapD = { d['midi']:d for d in cfg.key_mapL } keyMapD = { d['midi']:d for d in cfg.key_mapL }
axN = len(pitchL) axN = len(pitchL)
fig,axL = plt.subplots(axN,1) fig,axL = plt.subplots(axN,1,sharex=True)
if axN == 1: if axN == 1:
axL = [axL] axL = [axL]
fig.set_size_inches(18.5, 10.5*axN) fig.set_size_inches(18.5, 10.5*axN)
for ax,midi_pitch in zip(axL,pitchL): for ax,midi_pitch in zip(axL,pitchL):
plot_noise_region( ax,inDir, cfg.key_mapL, midi_pitch, analysisArgsD ) plot_us_db_curves( ax,inDir, keyMapD, midi_pitch, analysisArgsD, plotTakesFl=plotTakesFl, usMax=usMax )
if plotTakesFl:
plt.legend()
plt.show() plt.show()
def plot_all_noise_curves( inDir, cfg, pitchL=None ):
pitchFolderL = os.listdir(inDir)
if pitchL is None:
pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
fig,ax = plt.subplots()
for midi_pitch in pitchL:
print(midi_pitch)
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
minDurMs = cfg.analysisArgs['resampleMinDurMs']
minDb = cfg.analysisArgs['resampleMinDb'],
noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, scoreV.tolist(), takeIdL, minDurMs, minDb, noiseLimitPct )
if False:
ax.plot( usL[firstAudibleIdx], scoreV[firstAudibleIdx], markersize=10, marker='*', linestyle='None', color='red')
ax.plot( usL, scoreV, label="%i"%(midi_pitch) )
ax.set_xlabel('us')
else:
xL = [ (score,db,i) for i,(score,db) in enumerate(zip(scoreV,dbL)) ]
xL = sorted(xL, key=lambda x: x[1] )
scoreV,dbL,idxL = zip(*xL)
ax.plot( dbL[idxL[firstAudibleIdx]], scoreV[idxL[firstAudibleIdx]], markersize=10, marker='*', linestyle='None', color='red')
ax.plot( dbL, scoreV, label="%i"%(midi_pitch) )
ax.set_xlabel('db')
ax.set_ylabel("noise db %")
plt.legend()
plt.show()
def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
pitchFolderL = os.listdir(inDir)
if pitchL is None:
pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
okL = []
outPitchL = []
minDbL = []
maxDbL = []
for midi_pitch in pitchL:
print(midi_pitch)
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
okL.append(False)
takeId = len(set(takeIdL))-1
db_maxL = sorted(dbL)
maxDbL.append( np.mean(db_maxL[-5:]) )
usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId ])
if len(set(takeIdL)) == 3:
okL[-1] = True
elbow_us,elbow_db = elbow.find_elbow(usL,dbL)
minDbL.append(elbow_db)
outPitchL.append(midi_pitch)
p_dL = sorted( zip(outPitchL,minDbL,maxDbL,okL), key=lambda x: x[0] )
outPitchL,minDbL,maxDbL,okL = zip(*p_dL)
fig,ax = plt.subplots()
ax.plot(outPitchL,minDbL)
ax.plot(outPitchL,maxDbL)
keyMapD = { d['midi']:d for d in cfg.key_mapL }
for pitch,min_db,max_db,okFl in zip(outPitchL,minDbL,maxDbL,okL):
c = 'black' if okFl else 'red'
ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
plt.show()
def plot_min_db_manual( inDir, cfg ):
pitchL = list(cfg.manualMinD.keys())
outPitchL = []
maxDbL = []
minDbL = []
okL = []
anchorMinDbL = []
anchorMaxDbL = []
for midi_pitch in pitchL:
manual_take_id = cfg.manualMinD[midi_pitch][0]
manual_sample_idx = cfg.manualMinD[midi_pitch][1]
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
okL.append(False)
takeId = len(set(takeIdL))-1
# maxDb is computed on all takes (not just the specified take)
db_maxL = sorted(dbL)
max_db = np.mean(db_maxL[-4:])
maxDbL.append( max_db )
# get the us,db values for the specified take
usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==manual_take_id ])
# most pitches have 3 sample takes that do not
if len(set(takeIdL)) == 3 and manual_take_id == takeId:
okL[-1] = True
# min db from the sample index manually specified in cfg
manualMinDb = dbL[ manual_sample_idx ]
minDbL.append( manualMinDb )
outPitchL.append(midi_pitch)
if midi_pitch in cfg.manualAnchorPitchMinDbL:
anchorMinDbL.append( manualMinDb )
if midi_pitch in cfg.manualAnchorPitchMaxDbL:
anchorMaxDbL.append( max_db )
# Form the complete set of min/max db levels for each pitch by interpolating the
# db values between the manually selected anchor points.
interpMinDbL = np.interp( pitchL, cfg.manualAnchorPitchMinDbL, anchorMinDbL )
interpMaxDbL = np.interp( pitchL, cfg.manualAnchorPitchMaxDbL, anchorMaxDbL )
fig,ax = plt.subplots()
ax.plot(outPitchL,minDbL) # plot the manually selected minDb values
ax.plot(outPitchL,maxDbL) # plot the max db values
# plot the interpolated minDb/maxDb values
ax.plot(pitchL,interpMinDbL)
ax.plot(pitchL,interpMaxDbL)
keyMapD = { d['midi']:d for d in cfg.key_mapL }
for pitch,min_db,max_db,okFl in zip(outPitchL,minDbL,maxDbL,okL):
c = 'black' if okFl else 'red'
ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
with open("minInterpDb.json",'w') as f:
json.dump( { "pitchL":pitchL, "minDbL":list(interpMinDbL), "maxDbL":list(interpMaxDbL) }, f )
plt.show()
def plot_min_max_db( inDir, cfg, pitchL=None ):
pitchFolderL = os.listdir(inDir)
if pitchL is None:
pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
maxDbL = []
minDbL = []
for midi_pitch in pitchL:
print(midi_pitch)
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
minDurMs = cfg.analysisArgs['resampleMinDurMs']
minDb = cfg.analysisArgs['resampleMinDb'],
noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )
minDbL.append( dbL[firstAudibleIdx] )
dbL = sorted(dbL)
x = np.mean(dbL[-3:])
x = np.max(dbL)
maxDbL.append( x )
fig,ax = plt.subplots()
fig.set_size_inches(18.5, 10.5)
p_dL = sorted( zip(pitchL,maxDbL), key=lambda x: x[0] )
pitchL,maxDbL = zip(*p_dL)
ax.plot(pitchL,maxDbL)
ax.plot(pitchL,minDbL)
for pitch,db in zip(pitchL,maxDbL):
keyMapD = { d['midi']:d for d in cfg.key_mapL }
ax.text( pitch, db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']))
plt.show()
def estimate_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=0.5, pitchL=None ):
pitchFolderL = os.listdir(inDir)
if pitchL is None:
pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
mapD = {} # pitch:{ loDb: { hiDb, us_avg, us_cls, us_std, us_min, us_max, db_avg, db_std, cnt }}
# where: cnt=count of valid sample points in this db range
# us_cls=us of closest point to center of db range
dbS = set() # { (loDb,hiDb) } track the set of db ranges
for pitch in pitchL:
print(pitch)
# get the sample measurements for pitch
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
# calc the fit to local straight line curve fit at each point
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
minDurMs = cfg.analysisArgs['resampleMinDurMs']
minDb = cfg.analysisArgs['resampleMinDb'],
noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
# get the set of samples that are not valid (too short, too quiet, too noisy)
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )
mapD[ pitch ] = {}
# get the count of db ranges
N = int(round((maxMapDb - minMapDb) / incrMapDb)) + 1
# for each db range
for i in range(N):
loDb = minMapDb + (i*incrMapDb)
hiDb = loDb + incrMapDb
dbS.add((loDb,hiDb))
# get the valid (pulse,db) pairs for this range
u_dL = [(us,db) for i,(us,db) in enumerate(zip(usL,dbL)) if i not in skipIdxL and loDb<=db and db<hiDb ]
us_avg = 0
us_cls = 0
us_std = 0
us_min = 0
us_max = 0
db_avg = 0
db_std = 0
if len(u_dL) == 0:
print("No valid samples for pitch:",pitch," db range:",loDb,hiDb)
else:
us0L,db0L = zip(*u_dL)
if len(us0L) == 1:
us_avg = us0L[0]
us_cls = us_avg
us_min = us_avg
us_max = us_avg
db_avg = db0L[0]
elif len(us0L) > 1:
us_avg = np.mean(us0L)
us_cls = us0L[ np.argmin(np.abs(np.array(db0L)-(loDb - (hiDb-loDb)/2.0 ))) ]
us_min = np.min(us0L)
us_max = np.max(us0L)
us_std = np.std(us0L)
db_avg = np.mean(db0L)
db_std = np.std(db0L)
us_avg = int(round(us_avg))
mapD[pitch][loDb] = { 'hiDb':hiDb, 'us_avg':us_avg, 'us_cls':us_cls, 'us_std':us_std,'us_min':us_min,'us_max':us_max, 'db_avg':db_avg, 'db_std':db_std, 'cnt':len(u_dL) }
return mapD, list(dbS)
def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0, pitchL=None ):
fig,ax = plt.subplots()
mapD, dbRefL = estimate_us_to_db_map( inDir, cfg, minMapDb, maxMapDb, incrMapDb, pitchL )
# for each pitch
for pitch, dbD in mapD.items():
u_dL = [ (d['us_avg'],d['us_cls'],d['db_avg'],d['us_std'],d['us_min'],d['us_max'],d['db_std']) for loDb, d in dbD.items() if d['us_avg'] != 0 ]
# get the us/db lists for this pitch
usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
# plot central curve and std dev's
p = ax.plot(usL,dbL, marker='.', label=str(pitch))
ax.plot(uscL,dbL, marker='x', label=str(pitch), color=p[0].get_color(), linestyle='None')
ax.plot(usL,np.array(dbL)+dbsL, color=p[0].get_color(), alpha=0.3)
ax.plot(usL,np.array(dbL)-dbsL, color=p[0].get_color(), alpha=0.3)
# plot us error bars
for db,us,uss,us_min,us_max in zip(dbL,usL,ussL,usnL,usxL):
ax.plot([us_min,us_max],[db,db], color=p[0].get_color(), alpha=0.3 )
ax.plot([us-uss,us+uss],[db,db], color=p[0].get_color(), alpha=0.3, marker='.', linestyle='None' )
plt.legend()
plt.show()
def report_take_ids( inDir ):
pitchDirL = os.listdir(inDir)
for pitch in pitchDirL:
pitchDir = os.path.join(inDir,pitch)
takeDirL = os.listdir(pitchDir)
if len(takeDirL) == 0:
print(pitch," directory empty")
else:
with open( os.path.join(pitchDir,'0','seq.json'), "rb") as f:
r = json.load(f)
if len(r['eventTimeL']) != 81:
print(pitch," ",len(r['eventTimeL']))
if len(takeDirL) != 3:
print("***",pitch,len(takeDirL))
def cache_us_db( inDir, cfg, outFn ):
pitch_usDbD = {}
pitchDirL = os.listdir(inDir)
for pitch in pitchDirL:
pitch = int(pitch)
print(pitch)
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
pitch_usDbD[pitch] = { 'usL':usL, 'dbL':dbL, 'durMsL':durMsL, 'takeIdL':takeIdL, 'holdDutyPctL': holdDutyPctL }
with open(outFn,"w") as f:
json.dump(pitch_usDbD,f)
def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
velMapD = {} # { pitch:[ us ] }
pitchDirL = os.listdir(inDir)
with open(cacheFn,"r") as f:
pitchUsDbD = json.load(f)
with open("minInterpDb.json","r") as f:
r = json.load(f)
minMaxDbD = { pitch:(minDb,maxDb) for pitch,minDb,maxDb in zip(r['pitchL'],r['minDbL'],r['maxDbL']) }
pitchL = sorted( [ int(pitch) for pitch in pitchUsDbD.keys()] )
for pitch in pitchL:
d = pitchUsDbD[str(pitch)]
usL = d['usL']
dbL = np.array(d['dbL'])
velMapD[pitch] = []
for i in range(dynLevelN+1):
db = minMaxDbD[pitch][0] + (i * (minMaxDbD[pitch][1] - minMaxDbD[pitch][0])/ dynLevelN)
usIdx = np.argmin( np.abs(dbL - db) )
velMapD[pitch].append( (usL[ usIdx ],db) )
with open("velMapD.json","w") as f:
json.dump(velMapD,f)
mtx = np.zeros((len(velMapD),dynLevelN+1))
print(mtx.shape)
for i,(pitch,usDbL) in enumerate(velMapD.items()):
for j in range(len(usDbL)):
mtx[i,j] = usDbL[j][1]
fig,ax = plt.subplots()
ax.plot(pitchL,mtx)
plt.show()
if __name__ == "__main__": if __name__ == "__main__":
inDir = sys.argv[1] inDir = sys.argv[1]
cfgFn = sys.argv[2] cfgFn = sys.argv[2]
pitch = int(sys.argv[3]) mode = sys.argv[3]
if len(sys.argv) <= 4:
pitchL = None
else:
pitchL = [ int(sys.argv[i]) for i in range(4,len(sys.argv)) ]
cfg = parse_yaml_cfg( cfgFn ) cfg = parse_yaml_cfg( cfgFn )
pitchL = [pitch] if mode == 'us_db':
plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True,usMax=None )
elif mode == 'noise':
plot_all_noise_curves( inDir, cfg, pitchL )
elif mode == 'min_max':
plot_min_max_db( inDir, cfg, pitchL )
elif mode == 'min_max_2':
plot_min_max_2_db( inDir, cfg, pitchL )
elif mode == 'us_db_map':
plot_us_to_db_map( inDir, cfg, pitchL=pitchL )
elif mode == 'audacity':
rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )
elif mode == 'rpt_take_ids':
report_take_ids( inDir )
elif mode == 'manual_db':
plot_min_db_manual( inDir, cfg )
elif mode == 'gen_vel_map':
gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" )
elif mode == 'cache_us_db':
cache_us_db( inDir, cfg, "cache_us_db.json")
else:
print("Unknown mode:",mode)
plot_noise_regions_main( inDir, cfg, pitchL )
#rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )

File diff suppressed because one or more lines are too long

View File

@ -30,8 +30,8 @@ def rms_to_db( xV, rms_srate, dbLinRef ):
#dbWndN = int(round(refWndMs * rms_srate / 1000.0)) #dbWndN = int(round(refWndMs * rms_srate / 1000.0))
#dbRef = ref = np.mean(xV[0:dbWndN]) #dbRef = ref = np.mean(xV[0:dbWndN])
#print("DB REF:",dbLinRef) #print("DB REF:",dbLinRef, min(xV), np.argmin(xV))
rmsDbV = 20.0 * np.log10( xV / dbLinRef ) rmsDbV = 20.0 * np.log10( (xV+np.nextafter(0,1)) / dbLinRef )
return rmsDbV return rmsDbV
@ -41,7 +41,9 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef ):
hopSmpN = int(round( hopMs * srate / 1000.0)) hopSmpN = int(round( hopMs * srate / 1000.0))
xN = xV.shape[0] xN = xV.shape[0]
yN = int(((xN - wndSmpN) / hopSmpN) + 1) yN = int(((xN - wndSmpN) / hopSmpN) + 1)
assert( yN > 0) assert( yN > 0)
yV = np.zeros( (yN, ) ) yV = np.zeros( (yN, ) )
@ -62,7 +64,7 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef ):
j += 1 j += 1
rms_srate = srate / hopSmpN rms_srate = srate / hopSmpN
return rms_to_db( yV, rms_srate, dbLinRef ), rms_srate return rms_to_db( yV[0:j], rms_srate, dbLinRef ), rms_srate
def audio_stft_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, spectrumIdx ): def audio_stft_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, spectrumIdx ):
@ -219,12 +221,19 @@ def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
if qualityCoeff > qmax: if qualityCoeff > qmax:
qmax = qualityCoeff qmax = qualityCoeff
durAvgDb = (np.mean(r.rmsDbV[bi:ei]) + np.mean(r.tdRmsDbV[bi:ei]))/2.0 if ei-bi == 0:
tdRmsDb_v = 0.0 if bi >= len(r.tdRmsDbV) else np.mean(r.tdRmsDbV[bi])
hmRmsDb_v = 0.0 if bi >= len(r.rmsDbV) else np.mean(r.rmsDbV[bi])
durAvgDb = (hmRmsDb_v + tdRmsDb_v)/2.0
else:
tdRmsDb_u = 0.0 if ei >= len(r.tdRmsDbV) else np.mean(r.tdRmsDbV[bi:ei])
hmRmsDb_u = 0.0 if ei >= len(r.rmsDbV) else np.mean(r.rmsDbV[bi:ei])
durAvgDb = (hmRmsDb_u + tdRmsDb_u)/2.0
statsL.append( types.SimpleNamespace(**{'begSmpSec':begSmpIdx/srate,'endSmpSec':endSmpIdx/srate,'pkSmpSec':pkSmpIdx/srate,'durMs':durMs, 'pkDb':r.pkDbL[i], 'pulse_us':r.pkUsL[i], 'quality':qualityCoeff, 'durAvgDb':durAvgDb })) statsL.append( types.SimpleNamespace(**{'begSmpSec':begSmpIdx/srate,'endSmpSec':endSmpIdx/srate,'pkSmpSec':pkSmpIdx/srate,'durMs':durMs, 'pkDb':r.pkDbL[i], 'pulse_us':r.pkUsL[i], 'quality':qualityCoeff, 'durAvgDb':durAvgDb }))
for i,r in enumerate(statsL): for i,r in enumerate(statsL):
statsL[i].quality /= qmax statsL[i].quality = 0 if qmax <= 0 else statsL[i].quality / qmax
return statsL return statsL
@ -284,6 +293,42 @@ def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300
return { "td":tdD, "hm":hmD } return { "td":tdD, "hm":hmD }
def rms_analyze_one_rt_note_wrap( audioDev, annBegMs, annEndMs, midi_pitch, noteOffDurMs, rmsAnalysisD ):
resD = None
buf_result = audioDev.linear_buffer()
if buf_result:
sigV = buf_result.value
# get the annotated begin and end of the note as sample indexes into sigV
bi = int(round(annBegMs * audioDev.srate / 1000))
ei = int(round(annEndMs * audioDev.srate / 1000))
# calculate half the length of the note-off duration in samples
noteOffSmp_o_2 = int(round( (noteOffDurMs/2) * audioDev.srate / 1000))
# widen the note analysis space noteOffSmp_o_2 samples pre/post the annotated begin/end of the note
bi = max(0,bi - noteOffSmp_o_2)
ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1)
ar = types.SimpleNamespace(**rmsAnalysisD)
# shift the annotatd begin/end of the note to be relative to index bi
begMs = noteOffSmp_o_2 * 1000 / audioDev.srate
endMs = begMs + (annEndMs - annBegMs)
#print("MEAS:",begMs,endMs,bi,ei,sigV.shape,audioDev.is_recording_enabled(),ar)
# analyze the note
resD = rms_analyze_one_rt_note( sigV[bi:ei], audioDev.srate, begMs, endMs, midi_pitch, rmsWndMs=ar.rmsWndMs, rmsHopMs=ar.rmsHopMs, dbLinRef=ar.dbLinRef, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct )
#print( "hm:%4.1f %4i td:%4.1f %4i" % (resD['hm']['db'], resD['hm']['durMs'], resD['td']['db'], resD['td']['durMs']))
return resD
def calibrate_rms( sigV, srate, beg_ms, end_ms ): def calibrate_rms( sigV, srate, beg_ms, end_ms ):
@ -393,7 +438,6 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.
'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ], 'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ],
'pkUsL':r['pulseUsL'] }) 'pkUsL':r['pulseUsL'] })
statsL = note_stats(r,durDecayPct) statsL = note_stats(r,durDecayPct)
setattr(r,"statsL", statsL ) setattr(r,"statsL", statsL )
@ -492,7 +536,7 @@ def samples_to_linear_residual( usL, dbL, pointsPerLine=5 ):
assert( len(scoreL) == len(usL) ) assert( len(scoreL) == len(usL) )
return np.array(scoreL) return np.array(scoreL)
def write_audacity_label_files( inDir, analysisArgsD ): def write_audacity_label_files( inDir, analysisArgsD, reverseFl=True ):
pitchDirL = os.listdir(inDir) pitchDirL = os.listdir(inDir)
@ -520,7 +564,9 @@ def write_audacity_label_files( inDir, analysisArgsD ):
for i,s in enumerate(r.statsL): for i,s in enumerate(r.statsL):
label = "%i %4.1f %6.1f" % (i, s.pkDb, s.durMs ) noteIndex = len(r.statsL)-(i+1) if reverseFl else i
label = "%i %4.1f %6.1f" % (noteIndex, s.pkDb, s.durMs )
f.write("%f\t%f\t%s\n" % ( s.begSmpSec, s.endSmpSec, label )) f.write("%f\t%f\t%s\n" % ( s.begSmpSec, s.endSmpSec, label ))

162
velMapD.h Normal file
View File

@ -0,0 +1,162 @@
{
{ 23, { 12800, 12950, 13175, 13500, 13750, 14750, 15375, 17500, 23000, 37000, } },
{ 24, { 12425, 12800, 13175, 14225, 14750, 15500, 17500, 22500, 32000, 39000, } },
{ 25, { 14150, 14375, 14975, 14625, 15500, 16500, 20000, 28500, 40000, 40000, } },
{ 26, { 13000, 13175, 13500, 13700, 13925, 14250, 15000, 16250, 19000, 26500, } },
{ 27, { 13625, 13925, 14075, 14250, 14500, 14875, 15375, 16500, 18750, 25000, } },
{ 28, { 12625, 13750, 13775, 14225, 14500, 16500, 18000, 20000, 25500, 34000, } },
{ 29, { 12125, 12725, 13000, 12950, 14150, 15500, 16250, 17750, 21500, 28000, } },
{ 30, { 13175, 13325, 13550, 14450, 14875, 15500, 16250, 17750, 21500, 27000, } },
{ 31, { 13925, 14075, 14450, 14625, 15500, 16250, 16750, 17750, 19500, 23500, } },
{ 32, { 13250, 14150, 14975, 14750, 15250, 16000, 17500, 21000, 27000, 38000, } },
{ 33, { 11825, 13025, 14075, 14825, 14375, 14875, 16250, 17500, 22000, 28000, } },
{ 34, { 13025, 13375, 13325, 13775, 14375, 14500, 15250, 18000, 22000, 27000, } },
{ 35, { 11375, 12250, 12350, 12725, 14225, 13750, 15375, 17000, 20500, 25000, } },
{ 36, { 11750, 13875, 14125, 14225, 14675, 14750, 16500, 18500, 22500, 32000, } },
{ 37, { 12425, 12575, 13000, 13025, 13375, 15000, 16000, 18750, 25500, 35000, } },
{ 38, { 13750, 13875, 14075, 14600, 14750, 15500, 17750, 21500, 27500, 37000, } },
{ 39, { 11000, 12500, 12950, 13700, 14875, 15500, 16250, 20000, 26500, 37000, } },
{ 40, { 11525, 11750, 12125, 12500, 12875, 13500, 14625, 18250, 23500, 29000, } },
{ 41, { 11675, 11750, 12500, 13000, 13925, 15250, 17000, 20000, 26500, 36000, } },
{ 42, { 11875, 12000, 11975, 12050, 12275, 13375, 15000, 17250, 22000, 29000, } },
{ 43, { 11500, 11625, 11750, 11750, 12625, 12250, 13625, 16750, 19500, 25500, } },
{ 44, { 12425, 12500, 12750, 12650, 13000, 14000, 15250, 16500, 20000, 27000, } },
{ 45, { 11250, 11600, 11875, 12000, 12250, 13100, 14750, 15500, 18250, 25500, } },
{ 46, { 11450, 11525, 11600, 11625, 11875, 12250, 14000, 15750, 17750, 21500, } },
{ 47, { 11900, 11975, 12125, 12375, 13125, 14375, 15750, 18750, 22500, 28500, } },
{ 48, { 11750, 13100, 13325, 13625, 14300, 14975, 15750, 19000, 24000, 30000, } },
{ 49, { 11975, 12050, 12500, 12750, 13125, 14000, 17000, 20000, 25500, 40000, } },
{ 50, { 11625, 11525, 11750, 11825, 12125, 12375, 14750, 16250, 19000, 25500, } },
{ 51, { 12050, 12125, 12125, 12275, 12350, 12500, 12875, 16250, 18500, 22500, } },
{ 52, { 12950, 13025, 13125, 13175, 13250, 13500, 13875, 15750, 18000, 22000, } },
{ 53, { 10600, 10250, 10350, 10450, 10900, 11375, 13025, 14750, 18250, 26500, } },
{ 54, { 12650, 12625, 12725, 12800, 13000, 13625, 16250, 18500, 23000, 32000, } },
{ 55, { 11875, 12125, 12250, 12425, 12875, 13175, 13750, 17250, 20000, 26000, } },
{ 56, { 11625, 11750, 12000, 12200, 12500, 13125, 14375, 17000, 20500, 26500, } },
{ 57, { 11625, 11750, 12125, 12275, 12750, 14625, 16750, 20000, 25500, 39000, } },
{ 58, { 12000, 12500, 12750, 12875, 13100, 13375, 15000, 17750, 21000, 28000, } },
{ 59, { 11625, 11525, 12050, 13375, 13625, 14150, 16500, 21000, 24500, 30000, } },
{ 60, { 12250, 12250, 12375, 12350, 13000, 13500, 16000, 17750, 22000, 29000, } },
{ 61, { 11375, 11500, 11625, 11750, 12000, 12200, 12725, 13625, 17500, 21000, } },
{ 62, { 11600, 11675, 11825, 12125, 12650, 13375, 14375, 18500, 24500, 32000, } },
{ 63, { 12125, 12200, 12350, 12500, 13025, 13625, 16250, 18750, 24500, 36000, } },
{ 64, { 10550, 10650, 10850, 11250, 11875, 12250, 14000, 16250, 19500, 26500, } },
{ 65, { 12750, 12800, 12875, 13175, 13250, 14625, 14975, 17500, 20500, 26000, } },
{ 66, { 10750, 11000, 11250, 11500, 12000, 12875, 15375, 17000, 20500, 28500, } },
{ 67, { 10950, 11125, 11250, 11500, 11875, 13875, 15750, 17750, 23000, 37000, } },
{ 68, { 10150, 10300, 10550, 10800, 11125, 11875, 13000, 16000, 19000, 25000, } },
{ 69, { 11750, 11875, 12375, 12500, 12750, 13500, 16250, 18250, 23500, 31000, } },
{ 70, { 10700, 10850, 10950, 11125, 11625, 13875, 14500, 15750, 18750, 24500, } },
{ 71, { 10200, 10700, 11000, 11250, 11625, 14000, 14875, 16250, 22000, 27000, } },
{ 72, { 9800, 10100, 10400, 10550, 11000, 11625, 13000, 15500, 17750, 23000, } },
{ 73, { 10750, 10900, 11125, 11375, 11625, 12750, 14750, 15500, 18500, 23000, } },
{ 74, { 10300, 10450, 10600, 10850, 11250, 12000, 14250, 15000, 17500, 21000, } },
{ 75, { 10600, 10750, 10900, 11125, 12500, 14500, 14750, 15000, 21000, 31000, } },
{ 76, { 10200, 11625, 12375, 12875, 13500, 15750, 19000, 22500, 27500, 39000, } },
{ 77, { 10500, 10700, 11125, 11375, 11750, 14000, 14875, 16500, 20500, 27000, } },
{ 78, { 10450, 10800, 11000, 11625, 12000, 13125, 15500, 18250, 22000, 34000, } },
{ 79, { 12250, 13500, 14125, 14750, 16250, 17500, 19000, 24500, 31000, 40000, } },
{ 80, { 10400, 10450, 10750, 11125, 12125, 13375, 14750, 17750, 23000, 39000, } },
{ 81, { 10800, 10950, 11125, 11375, 12625, 13875, 14875, 16000, 19000, 23500, } },
{ 82, { 12000, 12375, 13750, 13750, 12625, 14000, 17000, 19000, 21000, 24500, } },
{ 83, { 12250, 12500, 13625, 13875, 14375, 16500, 17750, 20500, 25000, 35000, } },
{ 84, { 11500, 12000, 12250, 12500, 13125, 14250, 15375, 16750, 19500, 25500, } },
{ 85, { 10400, 10500, 10600, 11250, 12250, 13375, 15000, 16750, 20000, 26000, } },
{ 86, { 11500, 11750, 11875, 12000, 12250, 12500, 13500, 15000, 20500, 21000, } },
{ 87, { 10650, 11500, 13125, 13375, 13750, 14500, 16500, 18000, 20000, 24000, } },
{ 88, { 11375, 11375, 11500, 12375, 12000, 13375, 14500, 16500, 19000, 23000, } },
{ 89, { 9200, 10900, 11500, 12125, 22000, 12875, 14125, 16000, 19000, 26500, } },
{ 91, { 9450, 9950, 10000, 10150, 10600, 11250, 12125, 13875, 15250, 19000, } },
{ 92, { 9050, 9500, 9600, 10100, 10900, 11875, 13000, 16000, 20500, 31000, } },
{ 93, { 11250, 11375, 12000, 12375, 12875, 13625, 14250, 17500, 21500, 39000, } },
{ 94, { 11125, 11375, 11750, 13500, 14000, 14875, 15750, 17750, 22000, 25500, } },
{ 95, { 10200, 10350, 11500, 12250, 12500, 13125, 13875, 15250, 19000, 21000, } },
{ 96, { 9050, 9550, 10100, 13875, 13000, 14000, 18500, 22000, 27000, 39000, } },
{ 97, { 11000, 12500, 13250, 13000, 13750, 15750, 15000, 18000, 19000, 22500, } },
{ 98, { 10400, 10850, 12125, 12125, 13250, 13875, 16000, 18750, 26500, 37000, } },
{ 99, { 11000, 12625, 13125, 14000, 15500, 16750, 19000, 21500, 25000, 36000, } },
{ 100, { 9650, 10450, 11500, 12375, 12500, 12875, 13500, 15500, 17500, 21500, } },
{ 101, { 10950, 11250, 11500, 11875, 12250, 12875, 13500, 14375, 22500, 39000, } },
}
{
23, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
24, {{ 0, 75 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
25, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
26, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
27, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
28, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
29, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
30, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
31, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
32, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
33, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
34, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
35, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
36, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
37, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
38, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
39, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
40, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
41, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
42, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
43, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
44, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
45, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
46, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
47, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
48, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
49, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
50, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
51, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
52, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
53, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
54, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
55, {{ 0, 50 }, { 22000, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
56, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
57, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
58, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
59, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
60, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
61, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
62, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
63, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
64, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
65, {{ 0, 50 }, { 17000, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
66, {{ 0, 53 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
67, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
68, {{ 0, 53 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
69, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
70, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
71, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
72, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
73, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
74, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
75, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
76, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
77, {{ 0, 50 }, { 15000, 60 }, { 19000, 70 }, { -1, -1 }, { -1, -1 }, }
78, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
79, {{ 0, 50 }, { 15000, 60 }, { 19000, 70 }, { -1, -1 }, { -1, -1 }, }
80, {{ 0, 45 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
81, {{ 0, 50 }, { 15000, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
82, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
83, {{ 0, 50 }, { 15000, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
84, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
85, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
86, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
87, {{ 0, 50 }, { 14000, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
88, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
89, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
91, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
92, {{ 0, 40 }, { 14000, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
93, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
94, {{ 0, 40 }, { 14000, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
95, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
96, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
97, {{ 0, 40 }, { 14000, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
98, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
99, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
100, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
101, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
106, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
}

1
velMapD.json Normal file

File diff suppressed because one or more lines are too long

53
velTableToDataStruct.py Normal file
View File

@ -0,0 +1,53 @@
import json
from common import parse_yaml_cfg
ymlFn = 'p_ac.yml'
ifn = 'velMapD.json'
ofn = 'velMapD.h'
with open(ofn,"wt") as f1:
with open(ifn,"r") as f:
d = json.load(f)
f1.write("{\n");
for key,velL in d.items():
f1.write("{ ")
f1.write( str(key) + ", { " )
for us,x in velL:
f1.write("%5i, " % (us))
f1.write("} },\n")
f1.write("}\n\n");
cfg = parse_yaml_cfg(ymlFn)
d = cfg.calibrateArgs['holdDutyPctD']
n = 0
for key,dutyL in d.items():
n = max(n, len(dutyL))
f1.write("{\n")
for key,dutyL in d.items():
f1.write( str(key)+", {")
for i,(us,duty) in enumerate(dutyL):
f1.write("{ %i, %i }, " % (us,duty))
for j in range(i,n):
f1.write("{ -1, -1 }, ")
f1.write("},\n");
f1.write("}\n");