Many changes and additions.
First working version of calibrate.py Replaced analysis parameter dbRefWndMs with dbLinRef rms_analysis.py : Added samples_to_linear_residual() plot_seq_1.py : initial commit.
This commit is contained in:
parent
fc1a0d8a61
commit
ca9580cd50
@ -126,6 +126,13 @@ class AudioDevice(object):
|
||||
|
||||
return res
|
||||
|
||||
def is_recording_enabled( self ):
|
||||
|
||||
if self.inStream is None:
|
||||
return False
|
||||
|
||||
return self.inStream.active == True
|
||||
|
||||
def record_enable( self, enableFl ):
|
||||
|
||||
# if the input stream has not already been configured
|
||||
|
93
calibrate.py
93
calibrate.py
@ -1,6 +1,6 @@
|
||||
import os,types,wave,json,array
|
||||
import numpy as np
|
||||
from rms_analysis import rms_analyze_one_note
|
||||
from rms_analysis import rms_analyze_one_rt_note
|
||||
|
||||
class Calibrate:
|
||||
def __init__( self, cfg, audio, midi, api ):
|
||||
@ -65,9 +65,9 @@ class Calibrate:
|
||||
if self.midi is not None:
|
||||
self.midi.send_all_notes_off()
|
||||
|
||||
if not self.playOnlyFl:
|
||||
self.audio.record_enable(False)
|
||||
self.audio.record_enable(False)
|
||||
|
||||
if not self.playOnlyFl:
|
||||
self._save_results()
|
||||
|
||||
def play(self,ms):
|
||||
@ -75,11 +75,15 @@ class Calibrate:
|
||||
if self.measD is None or len(self.measD) == 0:
|
||||
print("Nothing to play.")
|
||||
else:
|
||||
self.startMs = ms
|
||||
self.state = 'started'
|
||||
self.playOnlyFl = True
|
||||
self.nextStateChangeMs = ms + 500
|
||||
self.curPitchIdx = -1
|
||||
self.curTargetDbIdx = 0
|
||||
|
||||
self.audio.record_enable(True)
|
||||
|
||||
self._do_play_update()
|
||||
|
||||
def tick(self,ms):
|
||||
@ -102,6 +106,7 @@ class Calibrate:
|
||||
elif self.state == 'note_off':
|
||||
if self.playOnlyFl:
|
||||
if not self._do_play_update():
|
||||
self.stop(ms)
|
||||
self.state = 'stopped'
|
||||
else:
|
||||
if self._do_analysis(ms):
|
||||
@ -115,8 +120,27 @@ class Calibrate:
|
||||
self.state = 'started'
|
||||
|
||||
|
||||
def _calc_play_pulse_us( self, pitch, targetDb ):
|
||||
|
||||
pulseDbL = []
|
||||
for d in self.measD[ pitch ]:
|
||||
if d['targetDb'] == targetDb and d['matchFl']==True:
|
||||
pulseDbL.append( ( d['pulse_us'], d[self.cfg.dbSrcLabel]['db']) )
|
||||
|
||||
if len(pulseDbL) == 0:
|
||||
return -1
|
||||
|
||||
pulseL,dbL = zip(*pulseDbL)
|
||||
|
||||
# TODO: make a weighted average based on db error
|
||||
|
||||
return np.mean(pulseL)
|
||||
|
||||
def _do_play_update( self ):
|
||||
|
||||
if self.curPitchIdx >= 0:
|
||||
self._meas_note( self.cfg.pitchL[self.curPitchIdx], self.curPulseUs )
|
||||
|
||||
self.curPitchIdx +=1
|
||||
if self.curPitchIdx >= len(self.cfg.pitchL):
|
||||
self.curPitchIdx = 0
|
||||
@ -124,13 +148,10 @@ class Calibrate:
|
||||
if self.curTargetDbIdx >= len(self.cfg.targetDbL):
|
||||
return False
|
||||
|
||||
pitch = self.cfg.pitchL[ self.curPitchIdx ]
|
||||
pitch = self.cfg.pitchL[ self.curPitchIdx ]
|
||||
targetDb = self.cfg.targetDbL[ self.curTargetDbIdx ]
|
||||
self.curPulseUs = -1
|
||||
for d in self.measD[ pitch ]:
|
||||
if d['targetDb'] == targetDb and d['matchFl']==True:
|
||||
self.curPulseUs = d['pulse_us']
|
||||
break
|
||||
self.curPulseUs = self._calc_play_pulse_us( pitch, targetDb )
|
||||
self.curTargetDb = targetDb
|
||||
|
||||
if self.curPulseUs == -1:
|
||||
print("Pitch:%i TargetDb:%f not found." % (pitch,targetDb))
|
||||
@ -195,10 +216,47 @@ class Calibrate:
|
||||
#print("note-off: ",self.cfg.pitchL[ self.curPitchIdx])
|
||||
|
||||
|
||||
def _proportional_step( self, targetDb, dbL, pulseL ):
|
||||
|
||||
curPulse,curDb = self.pulseDbL[-1]
|
||||
|
||||
# get the point closest to the target db
|
||||
i = np.argmin( np.array(dbL) - targetDb )
|
||||
|
||||
# find the percentage difference to the target - based on the closest point
|
||||
pd = abs(curDb-targetDb) / abs(curDb - dbL[i])
|
||||
|
||||
#
|
||||
delta_pulse = pd * abs(curPulse - pulseL[i])
|
||||
print("prop:",pd,"delta_pulse:",delta_pulse)
|
||||
|
||||
return int(round(curPulse + np.sign(targetDb - curDb) * delta_pulse))
|
||||
|
||||
def _step( self, targetDb, dbL, pulseL ):
|
||||
|
||||
pulse0,db0 = self.pulseDbL[-2]
|
||||
pulse1,db1 = self.pulseDbL[-1]
|
||||
|
||||
# microseconds per decibel for the last two points
|
||||
us_per_db = abs(pulse0-pulse1) / abs(db0-db1)
|
||||
|
||||
if us_per_db == 0:
|
||||
us_per_db = 10 # ************************************** CONSTANT ***********************
|
||||
|
||||
# calcuate the decibels we need to move from the last point
|
||||
error_db = targetDb - db1
|
||||
|
||||
print("us_per_db:",us_per_db," error db:", error_db )
|
||||
|
||||
return pulse1 + us_per_db * error_db
|
||||
|
||||
|
||||
|
||||
|
||||
def _calc_next_pulse_us( self, targetDb ):
|
||||
|
||||
# sort pulseDb ascending on db
|
||||
self.pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
|
||||
#self.pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
|
||||
|
||||
|
||||
pulseL,dbL = zip(*self.pulseDbL)
|
||||
@ -223,6 +281,10 @@ class Calibrate:
|
||||
self.deltaDnMult = 1
|
||||
pu = np.interp([targetDb],dbL,pulseL)
|
||||
|
||||
if int(pu) in pulseL:
|
||||
pu = self._step(targetDb, dbL, pulseL )
|
||||
|
||||
|
||||
return max(min(pu,self.cfg.maxPulseUs),self.cfg.minPulseUs)
|
||||
|
||||
def _do_analysis(self,ms):
|
||||
@ -309,16 +371,18 @@ class Calibrate:
|
||||
|
||||
sigV = buf_result.value
|
||||
|
||||
|
||||
|
||||
# get the annotated begin and end of the note as sample indexes into sigV
|
||||
bi = int(round(annD['beg_ms'] * self.audio.srate / 1000))
|
||||
ei = int(round(annD['end_ms'] * self.audio.srate / 1000))
|
||||
|
||||
# calculate half the length of the note-off duration in samples
|
||||
noteOffSmp_o_2 = int(round(self.cfg.noteOffDurMs/2 * self.audio.srate / 1000))
|
||||
noteOffSmp_o_2 = int(round( (self.cfg.noteOffDurMs/2) * self.audio.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(noteOffSmp_o_2,sigV.shape[0]-1)
|
||||
ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1)
|
||||
|
||||
|
||||
ar = types.SimpleNamespace(**self.cfg.analysisD)
|
||||
@ -327,8 +391,11 @@ class Calibrate:
|
||||
begMs = noteOffSmp_o_2 * 1000 / self.audio.srate
|
||||
endMs = begMs + (annD['end_ms'] - annD['beg_ms'])
|
||||
|
||||
#print("MEAS:",begMs,endMs,bi,ei,sigV.shape,self.audio.is_recording_enabled(),ar)
|
||||
|
||||
|
||||
# analyze the note
|
||||
resD = rms_analyze_rt_one_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, dbRefWndMs=ar.dbRefWndMs, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct )
|
||||
|
||||
resD["pulse_us"] = pulse_us
|
||||
resD["midi_pitch"] = midi_pitch
|
||||
|
79
p_ac.py
79
p_ac.py
@ -10,20 +10,21 @@ from MidiDevice import MidiDevice
|
||||
from result import Result
|
||||
from common import parse_yaml_cfg
|
||||
from plot_seq import form_resample_pulse_time_list
|
||||
from plot_seq import get_resample_points_wrap
|
||||
from plot_seq import form_final_pulse_list
|
||||
from rt_note_analysis import RT_Analyzer
|
||||
from keyboard import Keyboard
|
||||
from calibrate import Calibrate
|
||||
|
||||
class AttackPulseSeq:
|
||||
""" Sequence a fixed chord over a list of attack pulse lengths."""
|
||||
""" Sequence a fixed pitch over a list of attack pulse lengths."""
|
||||
|
||||
def __init__(self, cfg, audio, api, noteDurMs=1000, pauseDurMs=1000 ):
|
||||
self.cfg = cfg
|
||||
self.audio = audio
|
||||
self.api = api
|
||||
self.outDir = None # directory to write audio file and results
|
||||
self.pitchL = None # chord to play
|
||||
self.pitch = None # pitch to paly
|
||||
self.pulseUsL = [] # one onset pulse length in microseconds per sequence element
|
||||
self.noteDurMs = noteDurMs # duration of each chord in milliseconds
|
||||
self.pauseDurMs = pauseDurMs # duration between end of previous note and start of next
|
||||
@ -38,9 +39,9 @@ class AttackPulseSeq:
|
||||
self.playOnlyFl = False
|
||||
self.rtAnalyzer = RT_Analyzer()
|
||||
|
||||
def start( self, ms, outDir, pitchL, pulseUsL, holdDutyPctL, playOnlyFl=False ):
|
||||
def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, playOnlyFl=False ):
|
||||
self.outDir = outDir # directory to write audio file and results
|
||||
self.pitchL = pitchL # chord to play
|
||||
self.pitch = pitch # note to play
|
||||
self.pulseUsL = pulseUsL # one onset pulse length in microseconds per sequence element
|
||||
self.holdDutyPctL = holdDutyPctL
|
||||
self.pulse_idx = 0
|
||||
@ -51,9 +52,6 @@ class AttackPulseSeq:
|
||||
self.beginMs = ms
|
||||
self.playOnlyFl = playOnlyFl
|
||||
|
||||
#for pitch in pitchL:
|
||||
# self.api.set_pwm_duty( pitch, self.holdDutyPct )
|
||||
# print("set PWM:%i"%(self.holdDutyPct))
|
||||
|
||||
# kpl if not playOnlyFl:
|
||||
self.audio.record_enable(True) # start recording audio
|
||||
@ -121,11 +119,10 @@ class AttackPulseSeq:
|
||||
self.next_ms = ms + self.noteDurMs
|
||||
self.state = 'note_off'
|
||||
|
||||
for pitch in self.pitchL:
|
||||
pulse_usec = int(self.pulseUsL[ self.pulse_idx ])
|
||||
self._set_duty_cycle( pitch, pulse_usec )
|
||||
self.api.note_on_us( pitch, pulse_usec )
|
||||
print("note-on:",pitch, self.pulse_idx, pulse_usec)
|
||||
pulse_usec = int(self.pulseUsL[ self.pulse_idx ])
|
||||
self._set_duty_cycle( self.pitch, pulse_usec )
|
||||
self.api.note_on_us( self.pitch, pulse_usec )
|
||||
print("note-on:",self.pitch, self.pulse_idx, pulse_usec)
|
||||
|
||||
def _note_off( self, ms ):
|
||||
self.eventTimeL[ self.pulse_idx ][1] = self.audio.buffer_sample_ms().value
|
||||
@ -135,15 +132,14 @@ class AttackPulseSeq:
|
||||
if self.playOnlyFl:
|
||||
begTimeMs = self.eventTimeL[ self.pulse_idx ][0]
|
||||
endTimeMs = self.eventTimeL[ self.pulse_idx ][1]
|
||||
self.rtAnalyzer.analyze_note( self.audio, self.pitchL[0], begTimeMs, endTimeMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
|
||||
self.rtAnalyzer.analyze_note( self.audio, self.pitch, begTimeMs, endTimeMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
|
||||
|
||||
self._send_note_off()
|
||||
|
||||
|
||||
def _send_note_off( self ):
|
||||
for pitch in self.pitchL:
|
||||
self.api.note_off( pitch )
|
||||
#print("note-off:",pitch,self.pulse_idx)
|
||||
self.api.note_off( self.pitch )
|
||||
#print("note-off:",self.pitch,self.pulse_idx)
|
||||
|
||||
def _disable(self):
|
||||
self.state = None
|
||||
@ -153,7 +149,7 @@ class AttackPulseSeq:
|
||||
|
||||
d = {
|
||||
"pulseUsL":self.pulseUsL,
|
||||
"pitchL":self.pitchL,
|
||||
"pitch":self.pitch,
|
||||
"noteDurMs":self.noteDurMs,
|
||||
"pauseDurMs":self.pauseDurMs,
|
||||
"holdDutyPctL":self.holdDutyPctL,
|
||||
@ -180,16 +176,16 @@ class CalibrateKeys:
|
||||
self.seq = AttackPulseSeq( cfg, audioDev, api, noteDurMs=cfg.noteDurMs, pauseDurMs=cfg.pauseDurMs )
|
||||
|
||||
self.pulseUsL = None
|
||||
self.chordL = None
|
||||
self.pitchL = None
|
||||
self.pitch_idx = -1
|
||||
|
||||
|
||||
def start( self, ms, chordL, pulseUsL, playOnlyFl=False ):
|
||||
if len(chordL) > 0:
|
||||
def start( self, ms, pitchL, pulseUsL, playOnlyFl=False ):
|
||||
if len(pitchL) > 0:
|
||||
self.pulseUsL = pulseUsL
|
||||
self.chordL = chordL
|
||||
self.pitchL = pitchL
|
||||
self.pitch_idx = -1
|
||||
self._start_next_chord( ms, playOnlyFl )
|
||||
self._start_next_note( ms, playOnlyFl )
|
||||
|
||||
|
||||
def stop( self, ms ):
|
||||
@ -205,31 +201,27 @@ class CalibrateKeys:
|
||||
|
||||
# if the sequencer is done playing
|
||||
if not self.seq.is_enabled():
|
||||
self._start_next_chord( ms, self.seq.playOnlyFl ) # ... else start the next sequence
|
||||
self._start_next_note( ms, self.seq.playOnlyFl ) # ... else start the next sequence
|
||||
|
||||
return None
|
||||
|
||||
def _start_next_chord( self, ms, playOnlyFl ):
|
||||
|
||||
def _start_next_note( self, ms, playOnlyFl ):
|
||||
|
||||
self.pitch_idx += 1
|
||||
|
||||
# if the last chord in chordL has been played ...
|
||||
if self.pitch_idx >= len(self.chordL):
|
||||
# if the last note in pitchL has been played ...
|
||||
if self.pitch_idx >= len(self.pitchL):
|
||||
self.stop(ms) # ... then we are done
|
||||
else:
|
||||
|
||||
pitchL = self.chordL[ self.pitch_idx ]
|
||||
pitch = self.pitchL[ self.pitch_idx ]
|
||||
|
||||
# be sure that the base directory exists
|
||||
outDir = os.path.expanduser( cfg.outDir )
|
||||
if not os.path.isdir( outDir ):
|
||||
os.mkdir( outDir )
|
||||
baseDir = os.path.expanduser( cfg.outDir )
|
||||
if not os.path.isdir( baseDir ):
|
||||
os.mkdir( baseDir )
|
||||
|
||||
# form the output directory as "<label>_<pitch0>_<pitch1> ... "
|
||||
dirStr = "_".join([ str(pitch) for pitch in pitchL ])
|
||||
|
||||
outDir = os.path.join(outDir, dirStr )
|
||||
outDir = os.path.join(baseDir, str(pitch) )
|
||||
|
||||
if not os.path.isdir(outDir):
|
||||
os.mkdir(outDir)
|
||||
@ -240,13 +232,16 @@ class CalibrateKeys:
|
||||
print(outDir_id,outDir)
|
||||
|
||||
# if this is not the first time this note has been sampled then get the resample locations
|
||||
if outDir_id != 0:
|
||||
self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs )
|
||||
if outDir_id == 0:
|
||||
self.pulseUsL = self.cfg.full_pulseL
|
||||
else:
|
||||
#self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs )
|
||||
self.pulseUsL = get_resample_points_wrap( baseDir, pitch, self.cfg.analysisArgs )
|
||||
|
||||
holdDutyPctL = self.cfg.holdDutyPctL
|
||||
holdDutyPctL = self.cfg.calibrateArgs['holdDutyPctD'][pitch]
|
||||
|
||||
if playOnlyFl:
|
||||
self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( outDir, pitchL[0], self.cfg.analysisArgs, take_id=None )
|
||||
self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( outDir, pitch, self.cfg.analysisArgs, take_id=None )
|
||||
|
||||
noteN = cfg.analysisArgs['auditionNoteN']
|
||||
self.pulseUsL = [ self.pulseUsL[ int(round(i*126.0/(noteN-1)))] for i in range(noteN) ]
|
||||
@ -258,7 +253,7 @@ class CalibrateKeys:
|
||||
os.mkdir(outDir)
|
||||
|
||||
# start the sequencer
|
||||
self.seq.start( ms, outDir, pitchL, self.pulseUsL, holdDutyPctL, playOnlyFl )
|
||||
self.seq.start( ms, outDir, pitch, self.pulseUsL, holdDutyPctL, playOnlyFl )
|
||||
|
||||
|
||||
def _calc_next_out_dir_id( self, outDir ):
|
||||
@ -356,8 +351,8 @@ class App:
|
||||
|
||||
|
||||
def calibrate_keys_start( self, ms, pitchRangeL ):
|
||||
chordL = [ [pitch] for pitch in range(pitchRangeL[0], pitchRangeL[1]+1)]
|
||||
self.cal_keys.start( ms, chordL, cfg.full_pulseL )
|
||||
pitchL = [ pitch for pitch in range(pitchRangeL[0], pitchRangeL[1]+1)]
|
||||
self.cal_keys.start( ms, pitchL, cfg.full_pulseL )
|
||||
|
||||
def play_keys_start( self, ms, pitchRangeL ):
|
||||
chordL = [ [pitch] for pitch in range(pitchRangeL[0], pitchRangeL[1]+1)]
|
||||
|
21
p_ac.yml
21
p_ac.yml
@ -27,10 +27,10 @@
|
||||
|
||||
|
||||
# MeasureSeq args
|
||||
outDir: "~/temp/p_ac_3c",
|
||||
outDir: "~/temp/p_ac_3e",
|
||||
noteDurMs: 1000,
|
||||
pauseDurMs: 1000,
|
||||
holdDutyPctL: [ [0,50], [22000,55] ],
|
||||
#holdDutyPctL: [ [0,50], [22000,55] ],
|
||||
|
||||
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],
|
||||
@ -55,11 +55,16 @@
|
||||
rmsAnalysisArgs: {
|
||||
rmsWndMs: 300, # length of the RMS measurment window
|
||||
rmsHopMs: 30, # RMS measurement inter window distance
|
||||
dbRefWndMs: 500, # length of initial portion of signal to use to calculate the dB reference level
|
||||
dbLinRef: 0.01, # length of initial portion of signal to use to calculate the dB reference level
|
||||
harmCandN: 5, # count of harmonic candidates to locate during harmonic based RMS analysis
|
||||
harmN: 3, # count of harmonics to use to calculate harmonic based RMS analysis
|
||||
durDecayPct: 40, # percent drop in RMS to indicate the end of a note
|
||||
},
|
||||
|
||||
resampleMinDb: 10.0, # note's less than this will be skipped
|
||||
resampleNoiseLimitPct: 1.0, #
|
||||
resampleMinDurMs: 800, # notes's whose duration is less than this will be skipped
|
||||
|
||||
minAttkDb: 7.0, # threshold of silence level
|
||||
maxDbOffset: 0.25, # travel down the from the max. note level by at most this amount to locate the max. peak
|
||||
maxDeltaDb: 1.5, # maximum db change between volume samples (changes greater than this will trigger resampling)
|
||||
@ -75,12 +80,12 @@
|
||||
calibrateArgs: {
|
||||
|
||||
outDir: "~/temp/calib0",
|
||||
outLabel: "test",
|
||||
outLabel: "test_1",
|
||||
|
||||
analysisD: {
|
||||
rmsWndMs: 300, # length of the RMS measurment window
|
||||
rmsHopMs: 30, # RMS measurement inter window distance
|
||||
dbRefWndMs: 500, # length of initial portion of signal to use to calculate the dB reference level
|
||||
dbLinRef: 0.01, # length of initial portion of signal to use to calculate the dB reference level
|
||||
harmCandN: 5, # count of harmonic candidates to locate during harmonic based RMS analysis
|
||||
harmN: 3, # count of harmonics to use to calculate harmonic based RMS analysis
|
||||
durDecayPct: 40 # percent drop in RMS to indicate the end of a note
|
||||
@ -90,11 +95,11 @@
|
||||
noteOffDurMs: 1000,
|
||||
|
||||
|
||||
pitchL: [ 50, 51, 52 ], # list of pitches
|
||||
targetDbL: [ 16, 20, 23 ], # list of target db
|
||||
pitchL: [ 44, 45, 46, 47, 48, 49, 50, 51 ], # list of pitches
|
||||
targetDbL: [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ], # list of target db
|
||||
|
||||
minMeasDurMs: 800, # minimum candidate note duration
|
||||
tolDbPct: 5.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
|
||||
minPulseUs: 8000, # min. allowable pulse us
|
||||
initPulseUs: 15000, # pulseUs for first note
|
||||
|
File diff suppressed because one or more lines are too long
@ -5,8 +5,9 @@ import matplotlib._color_data as mcd
|
||||
from matplotlib.pyplot import figure
|
||||
|
||||
from rms_analysis import calibrate_recording_analysis
|
||||
from rms_analysis import key_info_dictionary
|
||||
|
||||
def plot_by_pitch( inDir, pitch=None ):
|
||||
def plot_by_pitch( inDir, keyInfoD, pitch=None ):
|
||||
|
||||
anlD = calibrate_recording_analysis( inDir )
|
||||
jsonFn = os.path.join(inDir, "meas.json" )
|
||||
@ -97,7 +98,7 @@ def plot_by_pitch( inDir, pitch=None ):
|
||||
|
||||
|
||||
|
||||
axL[axi].set_title("pitch:%i " % (midi_pitch))
|
||||
axL[axi].set_title("pitch:%i %s" % (midi_pitch,keyInfoD[midi_pitch].type))
|
||||
|
||||
plt.legend()
|
||||
plt.show()
|
||||
@ -140,11 +141,13 @@ if __name__ == "__main__":
|
||||
|
||||
pitch = None
|
||||
inDir = sys.argv[1]
|
||||
if len(sys.argv) > 2:
|
||||
yamlFn = sys.argv[2]
|
||||
if len(sys.argv) > 3:
|
||||
pitch = int(sys.argv[2])
|
||||
|
||||
keyInfoD = key_info_dictionary( yamlCfgFn=yamlFn)
|
||||
#plot_all_notes( inDir )
|
||||
plot_by_pitch(inDir,pitch)
|
||||
plot_by_pitch(inDir,keyInfoD,pitch)
|
||||
#calibrate_recording_analysis( inDir )
|
||||
|
||||
|
||||
|
@ -51,6 +51,8 @@ def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
|
||||
|
||||
#print(rr.tdRmsDbV.shape,rr.rmsDbV.shape)
|
||||
|
||||
sL = []
|
||||
|
||||
if sel_note_r is None:
|
||||
print("ERROR: No min note found.")
|
||||
else:
|
||||
@ -65,7 +67,6 @@ def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
|
||||
rr.rmsDbV = rr.rmsDbV[bi:ei]
|
||||
offsSec = bi / rr.rms_srate
|
||||
|
||||
sL = []
|
||||
for r in statsL:
|
||||
begSmpIdx = int(round(r.begSmpSec * rr.rms_srate))
|
||||
endSmpIdx = int(round(r.endSmpSec * rr.rms_srate))
|
||||
@ -84,11 +85,11 @@ def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
|
||||
return rr,sL
|
||||
|
||||
|
||||
def plot_note_analysis( inDir ):
|
||||
def plot_note_audio_reanalysis( inDir ):
|
||||
|
||||
rmsWndMs=300
|
||||
rmsHopMs=30
|
||||
dbRefWndMs=500
|
||||
dbLinRef=0.001
|
||||
harmCandN=5
|
||||
harmN=3
|
||||
durDecayPct = 50
|
||||
@ -99,21 +100,21 @@ def plot_note_analysis( inDir ):
|
||||
take_id = int(pathL[-1])
|
||||
midi_pitch = int(pathL[-2])
|
||||
|
||||
r = rms_analysis_main( inDir, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbRefWndMs=dbRefWndMs, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
|
||||
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_analysis_dir( inDir, dirL ):
|
||||
def plot_note_audio_reanalysis_dir( inDir, dirL ):
|
||||
|
||||
|
||||
for folder in dirL:
|
||||
|
||||
path = os.path.join(inDir,str(folder),"0")
|
||||
|
||||
plot_note_analysis( path )
|
||||
plot_note_audio_reanalysis( path )
|
||||
|
||||
|
||||
|
||||
@ -127,7 +128,7 @@ def get_all_note_durations( inDir, cacheFn ):
|
||||
takeId = 0
|
||||
rmsWndMs=300
|
||||
rmsHopMs=30
|
||||
dbRefWndMs=500
|
||||
dbLinRef=0.001
|
||||
harmCandN=5
|
||||
harmN=3
|
||||
durDecayPct = 40
|
||||
@ -139,7 +140,7 @@ def get_all_note_durations( inDir, cacheFn ):
|
||||
|
||||
if os.path.isfile(os.path.join(takePath,'seq.json')):
|
||||
print(midi_pitch)
|
||||
r = rms_analysis_main( takePath, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbRefWndMs=dbRefWndMs, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
|
||||
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):
|
||||
@ -319,7 +320,7 @@ if __name__ == "__main__":
|
||||
pitchL = [ 30,31,32,33,34,35 ]
|
||||
pitchL = [ 70,71,72,73,74,75 ]
|
||||
|
||||
#plot_note_analysis_dir( "/home/kevin/temp/p_ac_3c",pitchL)
|
||||
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)
|
||||
@ -327,4 +328,4 @@ if __name__ == "__main__":
|
||||
|
||||
#plot_quiet_note_db(durFn,"p_ac.yml")
|
||||
|
||||
dump_hold_duty_pct( "/home/kevin/temp/p_ac_3c" )
|
||||
#dump_hold_duty_pct( "/home/kevin/temp/p_ac_3c" )
|
||||
|
48
plot_seq.py
48
plot_seq.py
@ -5,6 +5,7 @@ from common import parse_yaml_cfg
|
||||
from rms_analysis import rms_analysis_main
|
||||
from rms_analysis import select_first_stable_note_by_delta_db
|
||||
from rms_analysis import select_first_stable_note_by_dur
|
||||
from rms_analysis import samples_to_linear_residual
|
||||
|
||||
def is_nanV( xV ):
|
||||
|
||||
@ -40,6 +41,10 @@ def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
|
||||
|
||||
take_number = int(idir)
|
||||
|
||||
|
||||
if not os.path.isfile(os.path.join( inDir,idir, "seq.json")):
|
||||
continue
|
||||
|
||||
# analyze this takes audio and locate the note peaks
|
||||
r = rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
|
||||
|
||||
@ -234,9 +239,14 @@ def form_resample_pulse_time_list( inDir, analysisArgsD ):
|
||||
|
||||
return resampleUsL, pkDbL, pkUsL
|
||||
|
||||
def plot_curve( ax, pulseUsL, rmsDbV ):
|
||||
|
||||
coeff = np.polyfit(pulseUsL,rmsDbV,5)
|
||||
func = np.poly1d(coeff)
|
||||
|
||||
def plot_resample_pulse_times( inDir, analysisArgsD ):
|
||||
ax.plot( pulseUsL, func(pulseUsL), color='red')
|
||||
|
||||
def plot_resample_pulse_times_0( inDir, analysisArgsD ):
|
||||
|
||||
newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
|
||||
|
||||
@ -245,15 +255,43 @@ def plot_resample_pulse_times( inDir, analysisArgsD ):
|
||||
|
||||
fig,ax = plt.subplots()
|
||||
|
||||
ax.plot(pulseUsL,rmsDbV )
|
||||
ax.plot(pulseUsL,rmsDbV,marker='.' )
|
||||
|
||||
for us in newPulseUsL:
|
||||
ax.axvline( x = us )
|
||||
|
||||
ax.plot(velTblUsL,velTblDbL,marker='.',linestyle='None')
|
||||
|
||||
|
||||
plt.show()
|
||||
|
||||
def plot_resample_pulse_times( inDir, analysisArgsD ):
|
||||
|
||||
newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
|
||||
|
||||
midi_pitch = int( inDir.split("/")[-1] )
|
||||
velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
|
||||
|
||||
fig,axL = plt.subplots(2,1,gridspec_kw={'height_ratios': [2, 1]})
|
||||
|
||||
axL[0].plot(pulseUsL,rmsDbV,marker='.' )
|
||||
|
||||
#plot_curve( ax, velTblUsL,velTblDbL)
|
||||
|
||||
scoreV = samples_to_linear_residual( pulseUsL, rmsDbV)
|
||||
|
||||
axL[0].plot(pulseUsL,rmsDbV + scoreV)
|
||||
axL[0].plot(pulseUsL,rmsDbV + np.power(scoreV,2.0))
|
||||
axL[0].plot(pulseUsL,rmsDbV - np.power(scoreV,2.0))
|
||||
|
||||
axL[1].axhline(0.0,color='black')
|
||||
axL[1].axhline(1.0,color='black')
|
||||
axL[1].plot(pulseUsL,np.abs(scoreV * 100.0 / rmsDbV))
|
||||
axL[1].set_ylim((0.0,50))
|
||||
plt.show()
|
||||
|
||||
|
||||
|
||||
def find_min_max_peak_index( pkDbL, minDb, maxDbOffs ):
|
||||
"""
|
||||
Find the min db and max db peak.
|
||||
@ -348,7 +386,7 @@ def plot_spectrum( ax, srate, binHz, specV, midiPitch, harmN ):
|
||||
|
||||
ax.set_ylabel(str(midiPitch))
|
||||
|
||||
def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbRefWndMs=500 ):
|
||||
def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbLinRef=0.001 ):
|
||||
""" Plot the spectrum from one note (7th from last) in each attack pulse length sequence referred to by pitchL."""
|
||||
|
||||
plotN = len(pitchL)
|
||||
@ -369,7 +407,7 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR
|
||||
sigV = signalM / float(0x7fff)
|
||||
|
||||
# calc. the RMS envelope in the time domain
|
||||
rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs )
|
||||
rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
|
||||
|
||||
# locate the sample index of the peak of each note attack
|
||||
pkIdx0L = locate_peak_indexes( rms0DbV, rms0_srate, r['eventTimeL'] )
|
||||
@ -383,7 +421,7 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR
|
||||
|
||||
|
||||
# calc. the RMS envelope by taking the max spectral peak in each STFT window
|
||||
rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, spectrumSmpIdx)
|
||||
rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, spectrumSmpIdx)
|
||||
|
||||
# specV[] is the spectrum of the note at spectrumSmpIdx
|
||||
|
||||
|
248
plot_seq_1.py
Normal file
248
plot_seq_1.py
Normal file
@ -0,0 +1,248 @@
|
||||
import os, sys
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from common import parse_yaml_cfg
|
||||
import rms_analysis
|
||||
|
||||
def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
|
||||
|
||||
inDir = os.path.join(inDir,"%i" % (midi_pitch))
|
||||
|
||||
dirL = os.listdir(inDir)
|
||||
|
||||
pkL = []
|
||||
|
||||
# for each take in this directory
|
||||
for idir in dirL:
|
||||
|
||||
take_number = int(idir)
|
||||
|
||||
# analyze this takes audio and locate the note peaks
|
||||
r = rms_analysis.rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD )
|
||||
|
||||
# store the peaks in pkL[ (db,us) ]
|
||||
for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
|
||||
pkL.append( (db,us,stats.durMs,take_number) )
|
||||
|
||||
|
||||
|
||||
# sort the peaks on increasing attack pulse microseconds
|
||||
pkL = sorted( pkL, key= lambda x: x[1] )
|
||||
|
||||
# merge sample points that separated by less than 'minSampleDistUs' milliseconds
|
||||
#pkL = merge_close_sample_points( pkL, analysisArgsD['minSampleDistUs'] )
|
||||
|
||||
# split pkL
|
||||
pkDbL,pkUsL,durMsL,takeIdL = tuple(zip(*pkL))
|
||||
|
||||
return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
|
||||
|
||||
def select_resample_reference_indexes( noiseIdxL ):
|
||||
|
||||
resampleIdxS = set()
|
||||
|
||||
for i in noiseIdxL:
|
||||
resampleIdxS.add( i )
|
||||
resampleIdxS.add( i+1 )
|
||||
resampleIdxS.add( i-1 )
|
||||
|
||||
resampleIdxL = list(resampleIdxS)
|
||||
|
||||
# if a single sample point is left out of a region of
|
||||
# contiguous sample points then include this as a resample point
|
||||
for i in resampleIdxL:
|
||||
if i + 1 not in resampleIdxL and i + 2 in resampleIdxL: # BUG BUG BUG: Hardcoded constant
|
||||
resampleIdxL.append(i+1)
|
||||
|
||||
return resampleIdxL
|
||||
|
||||
def locate_resample_regions( usL, dbL, resampleIdxL ):
|
||||
|
||||
# locate regions of points to resample
|
||||
regionL = [] # (bi,ei)
|
||||
inRegionFl = False
|
||||
bi = None
|
||||
for i in range(len(usL)):
|
||||
if inRegionFl:
|
||||
if i not in resampleIdxL:
|
||||
regionL.append((bi,i-1))
|
||||
inRegionFl = False
|
||||
bi = None
|
||||
else:
|
||||
if i in resampleIdxL:
|
||||
inRegionFl = True
|
||||
bi = i
|
||||
|
||||
if bi is not None:
|
||||
regionL.append((bi,len(usL)-1))
|
||||
|
||||
# select points around and within the resample regions
|
||||
# to resample
|
||||
reUsL = []
|
||||
reDbL = []
|
||||
for bi,ei in regionL:
|
||||
|
||||
for i in range(bi,ei+2):
|
||||
if i == 0:
|
||||
us = usL[i]
|
||||
db = dbL[i]
|
||||
elif i >= len(usL):
|
||||
us = usL[i-1]
|
||||
db = dbL[i-1]
|
||||
else:
|
||||
us = usL[i-1] + (usL[i]-usL[i-1])/2
|
||||
db = dbL[i-1] + (dbL[i]-dbL[i-1])/2
|
||||
|
||||
reUsL.append(us)
|
||||
reDbL.append(db)
|
||||
|
||||
|
||||
return reUsL,reDbL
|
||||
|
||||
def get_dur_skip_indexes( durMsL, dbL, takeIdL, minDurMs, minDb ):
|
||||
|
||||
firstAudibleIdx = None
|
||||
firstNonSkipIdx = None
|
||||
skipIdxL = [ i for i,(ms,db) in enumerate(zip(durMsL,dbL)) if ms < minDurMs or db < minDb ]
|
||||
|
||||
# if a single sample point is left out of a region of
|
||||
# contiguous skipped points then skip this point also
|
||||
for i in range(len(durMsL)):
|
||||
if i not in skipIdxL and i-1 in skipIdxL and i+1 in skipIdxL:
|
||||
skipIdxL.append(i)
|
||||
|
||||
# find the first set of 3 contiguous samples that
|
||||
# are greater than minDurMs - all samples prior
|
||||
# to these will be skipped
|
||||
xL = []
|
||||
for i in range(len(durMsL)):
|
||||
if i in skipIdxL:
|
||||
xL = []
|
||||
else:
|
||||
xL.append(i)
|
||||
|
||||
if len(xL) == 3: # BUG BUG BUG: Hardcoded constant
|
||||
firstAudibleIdx = xL[0]
|
||||
break
|
||||
|
||||
|
||||
# decrease by one decibel to locate the first non-skip
|
||||
|
||||
# TODO: what if no note exists that is one decibel less
|
||||
# The recordings of very quiet notes do not give reliabel decibel measures
|
||||
# so this may not be the best backup criteria
|
||||
|
||||
if firstAudibleIdx is not None:
|
||||
i = firstAudibleIdx-1
|
||||
while abs(dbL[i] - dbL[firstAudibleIdx]) < 1.0: # BUG BUG BUG: Hardcoded constant
|
||||
i -= 1
|
||||
|
||||
firstNonSkipIdx = i
|
||||
|
||||
return skipIdxL, firstAudibleIdx, firstNonSkipIdx
|
||||
|
||||
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 )
|
||||
noiseIdxL = [ i for i in range(scoreV.shape[0]) if scoreV[i] > noiseLimitPct ]
|
||||
noiseL = [ (usL[i],dbL[i]) for i in noiseIdxL ]
|
||||
resampleIdxL = select_resample_reference_indexes( noiseIdxL )
|
||||
resampleIdxL = [ i for i in resampleIdxL if i >= firstNonSkipIdx ]
|
||||
resampleL = [ (usL[i],dbL[i]) for i in resampleIdxL ]
|
||||
reUsL,reDbL = locate_resample_regions( usL, dbL, resampleIdxL )
|
||||
|
||||
return reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx
|
||||
|
||||
def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
|
||||
|
||||
usL, dbL, durMsL,_,_ = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
|
||||
|
||||
reUsL,_,_,_,_,_,_ = get_resample_points( usL, dbL, durMsL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
|
||||
|
||||
return reUsL
|
||||
|
||||
def plot_noise_region( ax, inDir, keyMapD, midi_pitch, analysisArgsD ):
|
||||
|
||||
plotResampleFl = False
|
||||
plotTakesFl = True
|
||||
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
|
||||
|
||||
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
|
||||
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')
|
||||
|
||||
# plot the resample points
|
||||
if plotResampleFl:
|
||||
ax.plot( reUsL, reDbL, markersize=10, marker='x', linestyle='None', color='green')
|
||||
|
||||
# plot the noisy sample positions
|
||||
if noiseL:
|
||||
nUsL,nDbL = zip(*noiseL)
|
||||
ax.plot( nUsL, nDbL, marker='o', linestyle='None', color='black')
|
||||
|
||||
# plot the noisy sample positions and the neighbors included in the noisy region
|
||||
if resampleL:
|
||||
nUsL,nDbL = zip(*resampleL)
|
||||
ax.plot( nUsL, nDbL, marker='*', linestyle='None', color='red')
|
||||
|
||||
# plot actual sample points
|
||||
if plotTakesFl:
|
||||
for takeId in list(set(takeIdL)):
|
||||
xL,yL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId ])
|
||||
ax.plot(xL,yL, marker='.')
|
||||
for i,(x,y) in enumerate(zip(xL,yL)):
|
||||
ax.text(x,y,str(i))
|
||||
else:
|
||||
ax.plot(usL, dbL, marker='.')
|
||||
|
||||
# plot the duration skip points
|
||||
if durL:
|
||||
nUsL,nDbL = zip(*durL)
|
||||
ax.plot( nUsL, nDbL, marker='.', linestyle='None', color='yellow')
|
||||
|
||||
# plot the locations where the hold duty cycle changes with vertical black lines
|
||||
for us_duty in holdDutyPctL:
|
||||
us,duty = tuple(us_duty)
|
||||
if us > 0:
|
||||
ax.axvline(us,color='black')
|
||||
|
||||
# plot the 'minDb' reference line
|
||||
ax.axhline(analysisArgsD['resampleMinDb'] ,color='black')
|
||||
|
||||
|
||||
ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
|
||||
|
||||
def plot_noise_regions_main( inDir, cfg, pitchL ):
|
||||
|
||||
analysisArgsD = cfg.analysisArgs
|
||||
keyMapD = { d['midi']:d for d in cfg.key_mapL }
|
||||
axN = len(pitchL)
|
||||
fig,axL = plt.subplots(axN,1)
|
||||
if axN == 1:
|
||||
axL = [axL]
|
||||
fig.set_size_inches(18.5, 10.5*axN)
|
||||
|
||||
for ax,midi_pitch in zip(axL,pitchL):
|
||||
plot_noise_region( ax,inDir, cfg.key_mapL, midi_pitch, analysisArgsD )
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
inDir = sys.argv[1]
|
||||
cfgFn = sys.argv[2]
|
||||
pitch = int(sys.argv[3])
|
||||
|
||||
cfg = parse_yaml_cfg( cfgFn )
|
||||
|
||||
pitchL = [pitch]
|
||||
|
||||
plot_noise_regions_main( inDir, cfg, pitchL )
|
||||
|
||||
#rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )
|
76
plot_us_db_range.ipynb
Normal file
76
plot_us_db_range.ipynb
Normal file
File diff suppressed because one or more lines are too long
146
rms_analysis.py
146
rms_analysis.py
@ -26,15 +26,16 @@ def calc_harm_bins( srate, binHz, midiPitch, harmN ):
|
||||
|
||||
return fund_l_binL, fund_m_binL, fund_u_binL
|
||||
|
||||
def rms_to_db( xV, rms_srate, refWndMs ):
|
||||
def rms_to_db( xV, rms_srate, dbLinRef ):
|
||||
#dbWndN = int(round(refWndMs * rms_srate / 1000.0))
|
||||
#dbRef = ref = np.mean(xV[0:dbWndN])
|
||||
dbRef = refWndMs ######################################################### HACK HACK HACK HACK HACK
|
||||
rmsDbV = 20.0 * np.log10( xV / dbRef )
|
||||
|
||||
#print("DB REF:",dbLinRef)
|
||||
rmsDbV = 20.0 * np.log10( xV / dbLinRef )
|
||||
|
||||
return rmsDbV
|
||||
|
||||
def audio_rms( srate, xV, rmsWndMs, hopMs, refWndMs ):
|
||||
def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef ):
|
||||
|
||||
wndSmpN = int(round( rmsWndMs * srate / 1000.0))
|
||||
hopSmpN = int(round( hopMs * srate / 1000.0))
|
||||
@ -61,10 +62,10 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, refWndMs ):
|
||||
j += 1
|
||||
|
||||
rms_srate = srate / hopSmpN
|
||||
return rms_to_db( yV, rms_srate, refWndMs ), rms_srate
|
||||
return rms_to_db( yV, rms_srate, dbLinRef ), rms_srate
|
||||
|
||||
|
||||
def audio_stft_rms( srate, xV, rmsWndMs, hopMs, refWndMs, spectrumIdx ):
|
||||
def audio_stft_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, spectrumIdx ):
|
||||
|
||||
wndSmpN = int(round( rmsWndMs * srate / 1000.0))
|
||||
hopSmpN = int(round( hopMs * srate / 1000.0))
|
||||
@ -82,12 +83,12 @@ def audio_stft_rms( srate, xV, rmsWndMs, hopMs, refWndMs, spectrumIdx ):
|
||||
|
||||
|
||||
rms_srate = srate / hopSmpN
|
||||
mV = rms_to_db( mV, rms_srate, refWndMs )
|
||||
mV = rms_to_db( mV, rms_srate, dbLinRef )
|
||||
|
||||
return mV, rms_srate, specV, specHopIdx, binHz
|
||||
|
||||
|
||||
def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, midiPitch, harmCandN, harmN ):
|
||||
def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, midiPitch, harmCandN, harmN ):
|
||||
|
||||
wndSmpN = int(round( rmsWndMs * srate / 1000.0))
|
||||
hopSmpN = int(round( hopMs * srate / 1000.0))
|
||||
@ -116,7 +117,7 @@ def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, midiPitch, harmCandN
|
||||
|
||||
|
||||
rms_srate = srate / hopSmpN
|
||||
rmsV = rms_to_db( rmsV, rms_srate, dbRefWndMs )
|
||||
rmsV = rms_to_db( rmsV, rms_srate, dbLinRef )
|
||||
return rmsV, rms_srate, binHz
|
||||
|
||||
def measure_duration_ms( rmsV, rms_srate, peak_idx, end_idx, decay_pct ):
|
||||
@ -257,26 +258,20 @@ def key_info_dictionary( keyMapL=None, yamlCfgFn=None):
|
||||
|
||||
return kmD
|
||||
|
||||
def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=500, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
|
||||
sigV = np.squeeze(sigV)
|
||||
|
||||
# HACK HACK HACK HACK
|
||||
dbRefWndMs = 0.002 # HACK HACK HACK HACK
|
||||
# HACK HACK HACK HACK
|
||||
td_rmsDbV, td_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs )
|
||||
td_rmsDbV, td_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
|
||||
|
||||
begSmpIdx = int(round(begMs * td_srate/1000))
|
||||
endSmpIdx = int(round(endMs * td_srate/1000))
|
||||
|
||||
td_pk_idx = begSmpIdx + np.argmax(td_rmsDbV[begSmpIdx:endSmpIdx])
|
||||
|
||||
td_durMs = measure_duration_ms( td_rmsDbV, td_srate, td_pk_idx, len(sigV)-1, durDecayPct )
|
||||
|
||||
# HACK HACK HACK HACK
|
||||
dbRefWndMs = 0.01 # HACK HACK HACK HACK
|
||||
# HACK HACK HACK HACK
|
||||
|
||||
hm_rmsDbV, hm_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, midi_pitch, harmCandN, harmN )
|
||||
hm_rmsDbV, hm_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN )
|
||||
|
||||
begSmpIdx = int(round(begMs * hm_srate/1000))
|
||||
endSmpIdx = int(round(endMs * hm_srate/1000))
|
||||
@ -289,6 +284,7 @@ def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300
|
||||
|
||||
return { "td":tdD, "hm":hmD }
|
||||
|
||||
|
||||
def calibrate_rms( sigV, srate, beg_ms, end_ms ):
|
||||
|
||||
bi = int(round(beg_ms * srate / 1000))
|
||||
@ -321,11 +317,7 @@ def calibrate_recording_analysis( inDir ):
|
||||
|
||||
anlr = types.SimpleNamespace(**cfg.analysisD)
|
||||
|
||||
# HACK HACK HACK HACK
|
||||
dbRefWndMs = 0.002 # HACK HACK HACK HACK
|
||||
# HACK HACK HACK HACK
|
||||
|
||||
tdRmsDbV, td_srate = audio_rms( srate, sigV, anlr.rmsWndMs, anlr.rmsHopMs, dbRefWndMs )
|
||||
tdRmsDbV, td_srate = audio_rms( srate, sigV, anlr.rmsWndMs, anlr.rmsHopMs, anlr.dbLinRef )
|
||||
|
||||
# for each measured pitch
|
||||
for midi_pitch,measL in measD.items():
|
||||
@ -358,7 +350,7 @@ def calibrate_recording_analysis( inDir ):
|
||||
|
||||
|
||||
|
||||
def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=500, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
|
||||
seqFn = os.path.join( inDir, "seq.json")
|
||||
audioFn = os.path.join( inDir, "audio.wav")
|
||||
@ -371,16 +363,14 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
|
||||
srate, signalM = wavfile.read(audioFn)
|
||||
sigV = signalM / float(0x7fff)
|
||||
|
||||
tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs )
|
||||
tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
|
||||
|
||||
tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate, r['eventTimeL'])
|
||||
|
||||
rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, midi_pitch, harmCandN, harmN )
|
||||
rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN )
|
||||
|
||||
pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'] )
|
||||
|
||||
|
||||
|
||||
holdDutyPctL = None
|
||||
if 'holdDutyPct' in r:
|
||||
holdDutyPctL = [ (0, r['holdDutyPct']) ]
|
||||
@ -390,6 +380,7 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
|
||||
|
||||
r = types.SimpleNamespace(**{
|
||||
"audio_srate":srate,
|
||||
"eventTimeMsL":r['eventTimeL'],
|
||||
"tdRmsDbV": tdRmsDbV,
|
||||
"tdPkIdxL": tdPkIdxL,
|
||||
"tdPkDbL": [ tdRmsDbV[i] for i in tdPkIdxL ],
|
||||
@ -397,8 +388,6 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
|
||||
"rmsDbV":rmsDbV,
|
||||
"rms_srate":rms_srate,
|
||||
"pkIdxL":pkIdxL, # pkIdxL[ len(pulsUsL) ] - indexes into rmsDbV[] of peaks
|
||||
#"min_pk_idx":min_pk_idx,
|
||||
#"max_pk_idx":max_pk_idx,
|
||||
"eventTimeL":r['eventTimeL'],
|
||||
"holdDutyPctL":holdDutyPctL,
|
||||
'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ],
|
||||
@ -412,7 +401,7 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
|
||||
return r
|
||||
|
||||
|
||||
def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=500, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
|
||||
if os.path.isfile(cacheFn):
|
||||
print("READING analysis cache file: %s" % (cacheFn))
|
||||
@ -436,7 +425,7 @@ def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbRefWndMs
|
||||
path = os.path.join(inDir,folder,'0')
|
||||
|
||||
if os.path.isdir(path) and os.path.isfile(os.path.join(os.path.join(path,"seq.json"))):
|
||||
r = rms_analysis_main( path, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbRefWndMs=dbRefWndMs, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
|
||||
r = rms_analysis_main( path, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
|
||||
|
||||
rD[ midi_pitch ] = r
|
||||
|
||||
@ -445,3 +434,94 @@ def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbRefWndMs
|
||||
pickle.dump(rD,f)
|
||||
|
||||
return rD
|
||||
|
||||
|
||||
|
||||
def samples_to_linear_residual( usL, dbL, pointsPerLine=5 ):
|
||||
# Score the quality of each sampled point by measuring the
|
||||
# quality of fit to a local line.
|
||||
|
||||
scoreD = { us:[] for us in usL }
|
||||
|
||||
pointsPerLine = 5
|
||||
|
||||
i = pointsPerLine
|
||||
|
||||
# for each sampled point
|
||||
while i < len(usL):
|
||||
# beginning with sample at index 'pointsPerLine'
|
||||
if i >= pointsPerLine:
|
||||
|
||||
k = i - pointsPerLine
|
||||
|
||||
# get the x (us) and y (db) sample values
|
||||
xL,yL = zip(*[ ((usL[k+j],1.0), dbL[k+j]) for j in range(pointsPerLine)])
|
||||
xV = np.array(xL)
|
||||
yV = np.array(yL)
|
||||
|
||||
# fit the sampled point to a line
|
||||
m,c = np.linalg.lstsq(xV,yV,rcond=None)[0]
|
||||
|
||||
# calc the residual of the fit at each point
|
||||
resV = (m*xV+c)[:,0] - yV
|
||||
|
||||
# assign the residual to the associated point in scoreD[x]
|
||||
for j in range(pointsPerLine):
|
||||
scoreD[usL[k+j]].append(resV[j])
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
scoreL = []
|
||||
|
||||
# calc the mean of the residuals for each point
|
||||
# (most points were used in 'pointsPerLine' line estimations
|
||||
# and so they will have 'pointsPerLine' residual values)
|
||||
for us in usL:
|
||||
|
||||
resL = scoreD[us]
|
||||
|
||||
if len(resL) == 0:
|
||||
scoreL.append(0.0)
|
||||
elif len(resL) == 1:
|
||||
scoreL.append(resL[0])
|
||||
else:
|
||||
scoreL.append(np.mean(resL))
|
||||
|
||||
# their should be one mean resid. value for each sampled point
|
||||
assert( len(scoreL) == len(usL) )
|
||||
return np.array(scoreL)
|
||||
|
||||
def write_audacity_label_files( inDir, analysisArgsD ):
|
||||
|
||||
pitchDirL = os.listdir(inDir)
|
||||
|
||||
for pitchDir in pitchDirL:
|
||||
|
||||
folderL = pitchDir.split(os.sep)
|
||||
|
||||
midi_pitch = int(folderL[-1])
|
||||
|
||||
pitchDir = os.path.join(inDir,pitchDir)
|
||||
|
||||
takeDirL = os.listdir(pitchDir)
|
||||
|
||||
for takeFolder in takeDirL:
|
||||
|
||||
takeDir = os.path.join(pitchDir,takeFolder)
|
||||
|
||||
r = rms_analysis_main( takeDir, midi_pitch, **analysisArgsD )
|
||||
|
||||
labelFn = os.path.join(takeDir,"audacity.txt")
|
||||
|
||||
print("Writing:",labelFn)
|
||||
|
||||
with open(labelFn,"w") as f:
|
||||
|
||||
for i,s in enumerate(r.statsL):
|
||||
|
||||
label = "%i %4.1f %6.1f" % (i, s.pkDb, s.durMs )
|
||||
|
||||
f.write("%f\t%f\t%s\n" % ( s.begSmpSec, s.endSmpSec, label ))
|
||||
|
||||
|
||||
|
@ -33,7 +33,7 @@ class RT_Analyzer:
|
||||
|
||||
anlArgs = types.SimpleNamespace(**anlArgD)
|
||||
|
||||
rmsDbV, rms_srate, binHz = audio_harm_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbRefWndMs, midi_pitch, anlArgs.harmCandN, anlArgs.harmN )
|
||||
rmsDbV, rms_srate, binHz = audio_harm_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbLinRef, midi_pitch, anlArgs.harmCandN, anlArgs.harmN )
|
||||
|
||||
pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, [( begTimeMs, endTimeMs)] )
|
||||
|
||||
@ -46,7 +46,7 @@ class RT_Analyzer:
|
||||
|
||||
hm_db = rmsDbV[ pkIdxL[0] ]
|
||||
|
||||
tdRmsDbV, rms0_srate = audio_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbRefWndMs )
|
||||
tdRmsDbV, rms0_srate = audio_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbLinRef )
|
||||
|
||||
tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate, [( begTimeMs, endTimeMs)] )
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user