Compare commits

...

9 Commits

15 changed files with 1069 additions and 207 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.ipynb
*.pyc *.pyc
.ipynb_checkpoints .ipynb_checkpoints

68
ChordTester.py Normal file
View File

@ -0,0 +1,68 @@
import types
import time
class ChordTester:
def __init__( self, cfg, api ):
self.api = api
self.cfg = types.SimpleNamespace(**cfg.ChordTester)
self.nextMs = 0
self.isStartedFl = False
self.curNoteCnt = 0
self.curRepeatCnt = 0
self.isPlayingFl = False
def start( self ):
self.api.set_hold_duty_all( self.cfg.holdDuty )
if self.cfg.useVelTableFl:
self.api.set_vel_table_all( self.cfg.pitchL )
self.curNoteCnt = 0
self.curRepeatCnt = 0
self.isStartedFl = True
def stop( self ):
self.isStartedFl = False
self.api.all_notes_off()
def tick( self, ms ):
if self.isStartedFl and ms >= self.nextMs:
if self.isPlayingFl:
# turn notes off
for i in range(0,self.curNoteCnt+1):
self.api.note_off( self.cfg.pitchL[i])
time.sleep( 0.01 )
# repeat or advance the chord note count
self.curRepeatCnt += 1
if self.curRepeatCnt >= self.cfg.repeatCnt:
self.curRepeatCnt = 0
self.curNoteCnt += 1
if self.curNoteCnt >= len(self.cfg.pitchL):
self.isStartedFl = False
self.curNoteCnt = 0
self.isPlayingFl = False
self.nextMs = ms + self.cfg.pauseMs
else:
for i in range(0,self.curNoteCnt+1):
if self.cfg.useVelTableFl:
self.api.note_on_vel(self.cfg.pitchL[i], 45 )
else:
self.api.note_on_us(self.cfg.pitchL[i], self.cfg.atkUsec)
time.sleep( 0.02 )
self.nextMs = ms + self.cfg.durMs
self.isPlayingFl = True

View File

@ -136,10 +136,12 @@ class MidiDevice(object):
o_msgL = [] o_msgL = []
if self.mip is not None: if self.mip is not None:
midi_msg = self.mip.get_message() while True:
if midi_msg and midi_msg[0]: midi_msg = self.mip.get_message()
if not midi_msg or not midi_msg[0]:
break;
if self.monitorInFl: if self.inMonitorFl:
o_msgL.append( self._midi_data_to_text_msg(True,midi_msg[0]) ) o_msgL.append( self._midi_data_to_text_msg(True,midi_msg[0]) )
if self.throughFl and self.mop is not None: if self.throughFl and self.mop is not None:

119
NoteTester.py Normal file
View File

@ -0,0 +1,119 @@
import sys,os,types,json
from random import randrange
class NoteTester:
def __init__( self, cfg, api ):
self.cfg = cfg
self.api = api
r = types.SimpleNamespace(**cfg.NoteTester)
self.durMsL = [ randrange(r.minNoteDurMs, r.maxNoteDurMs) for _ in range(r.noteCount) ]
self.pauseMsL = [ randrange(r.minPauseDurMs, r.maxPauseDurMs) for _ in range(r.noteCount) ]
self.eventL = []
self.nextMs = 0 # next transition time
self.eventIdx = 0 # next event to play
self.noteOnFl = False # True if note is currently sounding
self.pitch = r.pitch #
self.filename = r.filename
self.isStartedFl = False
self.minAttackUsec = r.minAttackUsec
self.maxAttackUsec = r.maxAttackUsec
def start( self ):
self.eventIdx = 0
self.noteOnFl = False
self.nextMs = 0
self.isStartedFl = True
def stop( self ):
self.isStartedFl = False
self.write()
def tick( self, ms ):
if self.isStartedFl and ms > self.nextMs:
offsMs = 0
if self.noteOnFl:
self.noteOnFl = False
self.api.note_off( self.pitch )
offsMs = self.pauseMsL[ self.eventIdx ]
self.eventIdx += 1
print("off:%i ms" % (offsMs))
else:
usec = self.minAttackUsec + (int(self.eventIdx * 250) % int(self.maxAttackUsec - self.minAttackUsec))
decay_level = self.api.calc_decay_level( usec )
self.api.note_on_us( self.pitch, usec, decay_level )
offsMs = self.durMsL[ self.eventIdx ]
print("usec:%i %i dcy:%i" % (usec,offsMs, decay_level) )
self.noteOnFl = True
self.eventL.append( (ms, self.noteOnFl) )
self.nextMs = ms + offsMs
if self.eventIdx >= len(self.durMsL):
self.write();
self.isStartedFl = False
print("done % i" % (len(self.eventL)))
def write( self ):
with open(self.filename,"w") as f:
json.dump({ "eventL":self.eventL },f )
def note_tester_compare( nt_fn, logica_fn ):
eventL = []
logicaL = []
with open(nt_fn,"r") as f:
r = json.load(f)
eventL = r['eventL']
eventL = [ (ms-eventL[0][0], level ) for ms,level in eventL ]
with open(logica_fn,"r") as f:
logicaL = [ ( d['count']/16e3,d['level']) for d in json.load(f) if d['signal'] == 0 ]
logicaL = [ (ms-logicaL[0][0], level!=0 ) for ms,level in logicaL ]
print(len(eventL))
print(len(logicaL))
#edL = [ eventL[i][0] - eventL[i-1][0] for i in range(2,len(eventL)) ]
#ldL = [ logicaL[i][0] - logicaL[i-1][0] for i in range(2,len(logicaL)) ]
#print(edL[:10])
#print(ldL[:10])
durMs = 0
ms = 0
for i,(t0,t1) in enumerate(zip(eventL,logicaL)):
t = t0[0] # eventL[] time
dt = int(t - t1[0]) # diff between eventL[] and logicaL[] time
fl = ' ' if t0[1] == t1[1] else '*' # mark level mismatch with '*'
print("%5i %7i %4i %i %s" % (i,durMs,dt,t0[1],fl))
durMs = t-ms
ms = t
if __name__ == "__main__":
nt_fn = "note_tester.json"
logica_fn = sys.argv[1]
if len(sys.argv) > 2:
nt_fn = sys.argv[2]
note_tester_compare( nt_fn, logica_fn)

148
PolyNoteTester.py Normal file
View File

@ -0,0 +1,148 @@
import types
import time
from random import randrange
class PolyNoteTester:
def __init__( self, cfg, api ):
self.api = api
r = types.SimpleNamespace(**cfg.PolyNoteTester)
self.cfg = r
if r.mode == "simple":
print("mode:simple")
self.schedL = self._gen_simple_sched(r)
else:
print("mode:poly")
self.schedL = self._gen_sched(r)
self.schedL = sorted( self.schedL, key=lambda x: x[0] )
self.nextMs = 0 # next transition time
self.schedIdx = 0 # next event to play
self.isStartedFl = False
#self._report()
def _report( self ):
for t,cmd,pitch,atkUs in self.schedL:
print("%s %6i %3i %5i" % (cmd,t,pitch,atkUs))
def _gen_simple_sched( self, r ):
""" Play each note sequentially from lowest to highest. """
durMs = int(r.minNoteDurMs + (r.maxNoteDurMs - r.minNoteDurMs)/2)
ioMs = int(r.minInterOnsetMs + (r.maxInterOnsetMs - r.minInterOnsetMs)/2)
atkUs = int(r.minAttackUsec + (r.maxAttackUsec - r.minAttackUsec)/2)
schedL = []
t0 = 0
for pitch in range(r.minPitch,r.maxPitch+1):
schedL.append((t0,'on',pitch,atkUs))
schedL.append((t0+durMs,'off',pitch,0))
t0 += durMs + ioMs
return schedL
def _does_note_overlap( self, beg0Ms, end0Ms, beg1Ms, end1Ms ):
""" if note 0 is entirely before or after note 1 """
return not (beg0Ms > end1Ms or end0Ms < beg1Ms)
def _do_any_notes_overlap( self, begMs, endMs, begEndL ):
for beg1,end1 in begEndL:
if self._does_note_overlap(begMs,endMs,beg1,end1):
return True
return False
def _get_last_end_time( self, begEndL ):
end0 = 0
for beg,end in begEndL:
if end > end0:
end0 = end
return end0
def _gen_sched( self, r ):
pitchL = [ randrange(r.minPitch,r.maxPitch) for _ in range(r.noteCount) ]
durMsL = [ randrange(r.minNoteDurMs, r.maxNoteDurMs) for _ in range(r.noteCount) ]
ioMsL = [ randrange(r.minInterOnsetMs, r.maxInterOnsetMs) for _ in range(r.noteCount) ]
atkUsL = [ randrange(r.minAttackUsec, r.maxAttackUsec) for _ in range(r.noteCount) ]
schedL = []
pitchD = {} # pitch: [ (begMs,endMs) ]
t0 = 0
# for each pitch,dur,ioi,atkUs tuple
for pitch,durMs,interOnsetMs,atkUs in zip(pitchL,durMsL,ioMsL,atkUsL):
# calc note begin and end time
begMs = t0
endMs = t0 + durMs
# if this pitch hasn't yet been added to pitchD
if pitch not in pitchD:
pitchD[ pitch ] = [ (begMs,endMs) ]
else:
# if the proposed note overlaps with other notes for this pitch
if self._do_any_notes_overlap( begMs, endMs, pitchD[pitch] ):
# move this pitch past the last note
begMs = self._get_last_end_time( pitchD[pitch] ) + interOnsetMs
endMs = begMs + durMs
# add the new note to pitchD
pitchD[ pitch ].append( (begMs,endMs) )
# update the schedule
schedL.append( (begMs, 'on', pitch, atkUs))
schedL.append( (endMs, 'off', pitch, 0 ))
t0 += interOnsetMs
return schedL
def start( self ):
self.schedIdx = 0
self.nextMs = 0
self.api.set_hold_duty_all(self.cfg.holdDutyPct)
self.isStartedFl = True
def stop( self ):
self.isStartedFl = False
self.api.all_notes_off()
def tick( self, ms ):
while self.isStartedFl and ms >= self.nextMs and self.schedIdx < len(self.schedL):
t0,cmd,pitch,usec = self.schedL[self.schedIdx]
if cmd == 'on':
if pitch not in self.cfg.skipPitchL:
decay_level = self.api.calc_decay_level( usec )
self.api.note_on_us( pitch, usec, decay_level )
print("on %i %i %i" % (pitch,usec,decay_level))
elif cmd == 'off':
if pitch not in self.cfg.skipPitchL:
self.api.note_off( pitch )
print("off %i" % pitch)
self.schedIdx += 1
if self.schedIdx < len(self.schedL):
self.nextMs = ms + (self.schedL[self.schedIdx][0] - t0)
else:
self.isStartedFl = False
print("Done.")

View File

@ -27,10 +27,13 @@ Capture note 60 and 61 using the full_pulseL[] and holdDutyPctD{} from the p_ac.
## Plot Cheat Sheet ## Plot Cheat Sheet
![Plot Seq 1](doc/do_td_plot.png) ---
Print a specific pitch and take. Print a specific pitch and take.
![Plot Seq 1](doc/do_td_plot.png)
```` ````
python plot_seq.py p_ac.yml ~/temp/p_ac_3_of td_plot 60 2 python plot_seq.py p_ac.yml ~/temp/p_ac_3_of td_plot 60 2
@ -39,19 +42,20 @@ Print a specific pitch and take.
--- ---
![Multi Usec dB](doc/us_db.png)
Plot all the takes for a given pitch Plot all the takes for a given pitch
![Multi Usec dB](doc/us_db.png)
```` ````
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db 84 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db 84
```` ````
--- ---
Plot a specific set of pitches and takes.
![Overlapping USec dB](doc/us_db_takes.png) ![Overlapping USec dB](doc/us_db_takes.png)
Plot a specific set of pitches and takes.
```` ````
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_take 75 0 76 0 77 0 78 0 72 10 73 1 74 1 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_take 75 0 76 0 77 0 78 0 72 10 73 1 74 1
@ -59,9 +63,10 @@ python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_take 75 0 76 0 77 0 7
--- ---
Plot the last take from a list of pitches.
![Overlapping USec dB](doc/us_db_takes_last.png) ![Overlapping USec dB](doc/us_db_takes_last.png)
Plot the last take from a list of pitches.
```` ````
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_last 77 78 79 80 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_last 77 78 79 80
@ -70,9 +75,10 @@ python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_last 77 78 79 80
--- ---
Plot the time domain envelope for a specific set of pitches and takes.
![Multi Plot 1](doc/multi_plot.png) ![Multi Plot 1](doc/multi_plot.png)
Plot the time domain envelope for a specific set of pitches and takes.
```` ````
python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 60 3 60 4 60 5 python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 60 3 60 4 60 5
@ -81,9 +87,10 @@ plot_seq.py `do_td_multi_plot(inDir,cfg.analysisArgs,[(36,4), (48,2)] )
```` ````
--- ---
Plot the spectrum with harmonic location markers of a specific set of pitches and takes.
![Spectral Ranges](doc/plot_spectral_ranges.png) ![Spectral Ranges](doc/plot_spectral_ranges.png)
Plot the spectrum with harmonic location markers of a specific set of pitches and takes.
```` ````
# pitch0 takeId0 pitch1 takeId1 # pitch0 takeId0 pitch1 takeId1
@ -93,21 +100,21 @@ python plot_seq.py p_ac.yml ~/temp/p_ac_3_od plot_spectral_ranges 60 3
--- ---
![Usec dB Spread](doc/us_db_map.png)
Plot the microsecond variance to achieve a given decibel value. Plot the microsecond variance to achieve a given decibel value.
![Usec dB Spread](doc/us_db_map.png)
```` ````
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db_map 84 72 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db_map 84 72
```` ````
--- ---
![Resample](doc/resample_pulse_times.png)
Analyze all takes for given pitch and show the mean us/db curve and Analyze all takes for given pitch and show the mean us/db curve and
places where resampling may be necessary. places where resampling may be necessary.
![Resample](doc/resample_pulse_times.png)
```` ````
python plot_seq.py p_ac.yml ~/temp/p_ac_3_of resample_pulse_times 84 python plot_seq.py p_ac.yml ~/temp/p_ac_3_of resample_pulse_times 84
```` ````
@ -115,30 +122,30 @@ python plot_seq.py p_ac.yml ~/temp/p_ac_3_of resample_pulse_times 84
--- ---
![Min Max](doc/min_max_db.png)
Plot the min and max decibel values for specified pitches. Plot the min and max decibel values for specified pitches.
![Min Max](doc/min_max_db.png)
```` ````
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max 36 48 60 72 84 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max 36 48 60 72 84
```` ````
--- ---
![Min Max 2](doc/min_max_db_2.png)
Plot the min and max decibel values for specified pitches. Plot the min and max decibel values for specified pitches.
![Min Max 2](doc/min_max_db_2.png)
```` ````
# pitch0 pitch1 pitch2 pitch3 pitch4 takeId # pitch0 pitch1 pitch2 pitch3 pitch4 takeId
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max_2 36 48 60 72 84 2 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max_2 36 48 60 72 84 2
```` ````
--- ---
![Manual dB](doc/manual_db.png)
Plot the min and max decibel values for specified set of manually corrected pitches. Plot the min and max decibel values for specified set of manually corrected pitches.
![Manual dB](doc/manual_db.png)
```` ````
python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od manual_db python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od manual_db

148
VelTablePlayer.py Normal file
View File

@ -0,0 +1,148 @@
import json
from rt_note_analysis import RT_Analyzer
class VelTablePlayer:
def __init__( self, cfg, api, audio, holdDutyPctD, fn ):
self.cfg = cfg
self.api = api
self.audio = audio
self.rtAnalyzer = RT_Analyzer()
self.holdDutyPctD = holdDutyPctD
self.durMs = 500
self.mode = "across"
self.state = "off"
self.minPitch = 21
self.maxPitch = 108
self.velMapD = {}
self.curMaxPitch = self.maxPitch
self.curMinPitch = self.minPitch
self.curPitch = 21
self.curVelocity = 0
self.curEndMs = 0
self.curBegNoteMs = 0
self.curEndNoteMs = 0
with open(fn,"r") as f:
d = json.load(f)
for pitch,value in d.items():
self.velMapD[ int(pitch) ] = [ int(x[0]) for x in d[pitch] ]
assert self.minPitch in self.velMapD
assert self.maxPitch in self.velMapD
def start( self, minPitch, maxPitch, mode ):
self.curMaxPitch = maxPitch
self.curMinPitch = minPitch
self.curPitch = minPitch
self.curVelocity = 0
self.state = "note_on"
self.mode = mode
self.audio.record_enable(True) # start recording audio
def stop( self ):
self.curPitch = self.minPitch
self._all_notes_off()
self.audio.record_enable(False)
def tick( self, ms ):
if self.state == "off":
pass
elif self.state == "note_on":
self.state = self._note_on(ms)
elif self.state == "playing":
if ms >= self.curEndMs:
self.state = "note_off"
elif self.state == "note_off":
self.state = self._note_off(ms)
def _get_duty_cycle( self, pitch, usec ):
usDutyL = self.holdDutyPctD[pitch]
for i in range(len(usDutyL)):
if usDutyL[i][0] >= usec:
return usDutyL[i][1]
return usDutyL[-1][1]
def _calc_next_pitch( self ):
self.curPitch += 1
while self.curPitch not in self.velMapD and self.curPitch <= self.curMaxPitch:
self.curPitch+1
return self.curPitch <= self.curMaxPitch
def _get_next_note_params( self ):
usec = None
dutyPct = None
doneFl = False
if self.mode == "updown":
if self.curVelocity + 1 < len(self.velMapD[ self.curPitch ]):
self.curVelocity += 1
else:
if self._calc_next_pitch():
self.curVelocity = 0
else:
doneFl = True
else:
if self._calc_next_pitch():
self.curPitch += 1
else:
if self.curVelocity + 1 < len(self.velMapD[ self.curPitch ]):
self.curVelocity += 1
self.curPitch = self.curMinPitch
else:
doneFl = True
if doneFl:
self.audio.record_enable(False)
else:
usec = self.velMapD[self.curPitch][self.curVelocity]
dutyPct = self._get_duty_cycle( self.curPitch, usec )
return self.curPitch, usec, dutyPct
def _note_on( self, ms ):
pitch,usec,dutyPct = self._get_next_note_params()
if not usec:
return "off"
else:
print(self.curPitch,self.curVelocity,usec,dutyPct)
self.curBegNoteMs = self.audio.buffer_sample_ms().value
self.api.set_pwm_duty( pitch, dutyPct )
self.api.note_on_us( pitch, usec )
self.curEndMs = ms + self.durMs
return "playing"
def _note_off( self, ms ):
self.curEndNoteMs = self.audio.buffer_sample_ms().value
self.rtAnalyzer.analyze_note( self.audio, self.curPitch, self.curBegNoteMs, self.curEndNoteMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
self.api.note_off( self.curPitch )
return "note_on"
def _all_notes_off( self ):
if self.curPitch == 109:
self.state = 'off'
print('done')
else:
self.api.note_off( self.curPitch )
self.curPitch += 1

View File

@ -29,7 +29,7 @@ def fit_points_to_reference( usL, dbL, usRefL, dbRefL ):
return dbV return dbV
def find_elbow( usL, dbL, pointsPerLine=10 ): def find_elbow( usL, dbL, pointsPerLine=5 ):
ppl_2 = int(pointsPerLine/2) ppl_2 = int(pointsPerLine/2)
dL = [] dL = []
@ -58,7 +58,7 @@ def find_elbow( usL, dbL, pointsPerLine=10 ):
i += 1 i += 1
# find the max angle # find the max angle
i = np.argmax( dL ) i = np.argmax( dL )
# return the x,y coordinate of the first data point of the second line # return the x,y coordinate of the first data point of the second line
return (usL[i+ppl_2],dbL[i+ppl_2]) return (usL[i+ppl_2],dbL[i+ppl_2])

171
p_ac.py
View File

@ -19,6 +19,10 @@ from keyboard import Keyboard
from calibrate import Calibrate from calibrate import Calibrate
from rms_analysis import rms_analyze_one_rt_note_wrap from rms_analysis import rms_analyze_one_rt_note_wrap
from MidiFilePlayer import MidiFilePlayer from MidiFilePlayer import MidiFilePlayer
from VelTablePlayer import VelTablePlayer
from NoteTester import NoteTester
from PolyNoteTester import PolyNoteTester
from ChordTester import ChordTester
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."""
@ -33,7 +37,7 @@ 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.holdDutyPctD= None # { us:dutyPct } for each us in self.pulseUsL
self.silentNoteN = None 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'
@ -45,25 +49,28 @@ class AttackPulseSeq:
self.rtAnalyzer = RT_Analyzer() self.rtAnalyzer = RT_Analyzer()
def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, holdDutyPctD, 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.holdDutyPctD = holdDutyPctD
self.silentNoteN = 0 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
self.next_ms = ms + 500 # wait for 500ms to play the first note (this will guarantee that there is some empty space in the audio file before the first note) self.next_ms = ms + 500 # wait for 500ms to play the first note (this will guarantee that there is some empty space in the audio file before the first note)
self.eventTimeL = [[0,0] for _ in range(len(pulseUsL))] # initialize the event time self.eventTimeL = [[0,0] for _ in range(len(pulseUsL))] # initialize the event time
self.beginMs = ms self.beginMs = ms
self.playOnlyFl = playOnlyFl self.playOnlyFl = playOnlyFl
# kpl if not playOnlyFl: # kpl if not playOnlyFl:
self.audio.record_enable(True) # start recording audio self.audio.record_enable(True) # start recording audio
print("Hold Delay from end of attack:",cfg.defaultHoldDelayUsecs) #print("Hold Delay from end of attack:",cfg.defaultHoldDelayUsecs)
self.api.set_hold_delay(pitch,cfg.defaultHoldDelayUsecs) # self.api.set_hold_delay(pitch,cfg.defaultHoldDelayUsecs)
self.api.set_hold_duty(pitch,35)
print("Hold Duty Cycle=35.")
self.tick(ms) # play the first note self.tick(ms) # play the first note
@ -128,15 +135,36 @@ class AttackPulseSeq:
def _set_duty_cycle( self, pitch, pulseUsec ): def _set_duty_cycle( self, pitch, pulseUsec ):
if False:
dutyPct = self._get_duty_cycle( pulseUsec ) dutyPct = self._get_duty_cycle( pulseUsec )
if dutyPct != self.prevHoldDutyPct: if dutyPct != self.prevHoldDutyPct:
self.api.set_pwm_duty( pitch, dutyPct ) # self.api.set_pwm_duty( pitch, dutyPct )
print("Hold Duty:",dutyPct) # print("Hold Duty:",dutyPct)
pass
self.prevHoldDutyPct = dutyPct
self.prevHoldDutyPct = dutyPct
if False:
maxLevel = 64 # 225 # 64
minLevel = 24 # 89 # 24
maxUsec = 10000
minUsec = 2500
decayLevel = maxLevel
if pulseUsec < maxUsec and pulseUsec >= minUsec:
decayLevel = minLevel + ((pulseUsec-minUsec)/(maxUsec-minUsec)) * (maxLevel-minLevel)
else:
if pulseUsec < minUsec:
decayLevel = minLevel
decayLevel = int(decayLevel)
decayLevel = self.api.calc_decay_level( pulseUsec )
self.api.set_decay_level( pitch, decayLevel )
print("Decay Level:", decayLevel)
def _note_on( self, ms ): def _note_on( self, ms ):
self.eventTimeL[ self.pulse_idx ][0] = self.audio.buffer_sample_ms().value self.eventTimeL[ self.pulse_idx ][0] = self.audio.buffer_sample_ms().value
@ -144,9 +172,13 @@ class AttackPulseSeq:
self.state = 'note_off' self.state = 'note_off'
pulse_usec = int(self.pulseUsL[ self.pulse_idx ]) pulse_usec = int(self.pulseUsL[ self.pulse_idx ])
self._set_duty_cycle( self.pitch, pulse_usec )
# decay_level = self.api.calc_decay_level( pulse_usec )
#self._set_duty_cycle( self.pitch, pulse_usec )
self.api.note_on_us( self.pitch, pulse_usec ) self.api.note_on_us( self.pitch, pulse_usec )
print("note-on:",self.pitch, self.pulse_idx, pulse_usec) print("note-on:",self.pitch, self.pulse_idx, pulse_usec, "dcy:", decay_level)
def _note_off( self, ms ): def _note_off( self, ms ):
self.eventTimeL[ self.pulse_idx ][1] = self.audio.buffer_sample_ms().value self.eventTimeL[ self.pulse_idx ][1] = self.audio.buffer_sample_ms().value
@ -209,7 +241,7 @@ class CalibrateKeys:
self.pulseUsL = pulseUsL self.pulseUsL = pulseUsL
self.pitchL = pitchL self.pitchL = pitchL
self.pitch_idx = -1 self.pitch_idx = -1
self._start_next_note( ms, playOnlyFl ) self._start_next_seq( ms, playOnlyFl )
def stop( self, ms ): def stop( self, ms ):
@ -225,11 +257,11 @@ class CalibrateKeys:
# if the sequencer is done playing # if the sequencer is done playing
if not self.seq.is_enabled(): if not self.seq.is_enabled():
self._start_next_note( ms, self.seq.playOnlyFl ) # ... else start the next sequence self._start_next_seq( ms, self.seq.playOnlyFl ) # ... else start the next sequence
return None return None
def _start_next_note( self, ms, playOnlyFl ): def _start_next_seq( self, ms, playOnlyFl ):
self.pitch_idx += 1 self.pitch_idx += 1
@ -253,8 +285,6 @@ class CalibrateKeys:
# get the next available output directory id # get the next available output directory id
outDir_id = self._calc_next_out_dir_id( outDir ) outDir_id = self._calc_next_out_dir_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) or self.cfg.useFullPulseListFl: if (outDir_id == 0) or self.cfg.useFullPulseListFl:
self.pulseUsL = self.cfg.full_pulseL self.pulseUsL = self.cfg.full_pulseL
@ -265,9 +295,10 @@ class CalibrateKeys:
holdDutyPctL = self.cfg.calibrateArgs['holdDutyPctD'][pitch] holdDutyPctL = self.cfg.calibrateArgs['holdDutyPctD'][pitch]
# if playback of the current calibration was requested
if playOnlyFl: if playOnlyFl:
# form the calibrated pulse list
self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( baseDir, pitch, self.cfg.analysisArgs, take_id=None ) self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( baseDir, pitch, self.cfg.analysisArgs, take_id=None )
noteN = cfg.analysisArgs['auditionNoteN'] noteN = cfg.analysisArgs['auditionNoteN']
@ -318,6 +349,10 @@ class App:
self.keyboard = None self.keyboard = None
self.calibrate = None self.calibrate = None
self.midiFilePlayer = None self.midiFilePlayer = None
self.velTablePlayer = None
self.noteTester = None
self.polyNoteTester = None
self.chordTester = None
def setup( self, cfg ): def setup( self, cfg ):
self.cfg = cfg self.cfg = cfg
@ -369,6 +404,15 @@ class App:
self.midiFilePlayer = MidiFilePlayer( cfg, self.api, self.midiDev, cfg.midiFileFn ) self.midiFilePlayer = MidiFilePlayer( cfg, self.api, self.midiDev, cfg.midiFileFn )
self.velTablePlayer = VelTablePlayer( cfg, self.api, self.audioDev, self.cfg.calibrateArgs['holdDutyPctD'], "velMapD.json")
self.noteTester = NoteTester(cfg,self.api)
self.polyNoteTester = PolyNoteTester(cfg,self.api)
self.chordTester = ChordTester(cfg,self.api)
return res return res
def tick( self, ms ): def tick( self, ms ):
@ -388,13 +432,30 @@ class App:
if self.midiFilePlayer: if self.midiFilePlayer:
self.midiFilePlayer.tick(ms) self.midiFilePlayer.tick(ms)
if self.midiDev:
msgL = self.midiDev.get_input()
for msg in msgL:
print(msg['value']);
if self.velTablePlayer:
self.velTablePlayer.tick(ms)
if self.noteTester:
self.noteTester.tick(ms)
if self.polyNoteTester:
self.polyNoteTester.tick(ms)
if self.chordTester:
self.chordTester.tick(ms)
def audio_dev_list( self, ms ): def audio_dev_list( self, ms ):
if self.audioDev is not None: if self.audioDev is not None:
portL = self.audioDev.get_port_list( True ) portL = self.audioDev.get_port_list( True )
for port in portL: for port in portL:
print("chs:%4i label:%s" % (port['chN'],port['label'])) print("chs:%4i label:'%s'" % (port['chN'],port['label']))
def midi_dev_list( self, ms ): def midi_dev_list( self, ms ):
d = self.midiDev.get_port_list( True ) d = self.midiDev.get_port_list( True )
@ -447,6 +508,33 @@ class App:
def midi_file_player_stop( self, ms ): def midi_file_player_stop( self, ms ):
self.midiFilePlayer.stop(ms) self.midiFilePlayer.stop(ms)
def vel_table_updown( self, ms, argL):
self.velTablePlayer.start(argL[0],argL[1],"updown")
def vel_table_across( self, ms, argL ):
self.velTablePlayer.start(argL[0],argL[1],"across")
def vel_table_stop( self, ms ):
self.velTablePlayer.stop()
def note_tester_start( self, ms ):
self.noteTester.start();
def note_tester_stop( self, ms ):
self.noteTester.stop();
def poly_note_tester_start( self, ms ):
self.polyNoteTester.start();
def poly_note_tester_stop( self, ms ):
self.polyNoteTester.stop();
def chord_tester_start( self, ms ):
self.chordTester.start()
def chord_tester_stop( self, ms ):
self.chordTester.stop()
def pedal_down( self, ms ): def pedal_down( self, ms ):
print("pedal_down") print("pedal_down")
self.midiDev.send_controller(64, 100 ) self.midiDev.send_controller(64, 100 )
@ -454,6 +542,12 @@ class App:
def pedal_up( self, ms ): def pedal_up( self, ms ):
print("pedal_up"); print("pedal_up");
self.midiDev.send_controller(64, 0 ) self.midiDev.send_controller(64, 0 )
def all_notes_off(self, ms):
self.api.all_notes_off();
def check_for_errors(self,ms):
self.api.check_for_serial_errors()
def quit( self, ms ): def quit( self, ms ):
if self.api: if self.api:
@ -587,8 +681,19 @@ class Shell:
'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_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."}, 'f':{ "func":"midi_file_player_stop", "minN":0, "maxN":0, "help":"Stop the MIDI file."},
'V':{ "func":"vel_table_updown", "minN":2, "maxN":2, "help":"Play Velocity Table up/down - across."},
'A':{ "func":"vel_table_across", "minN":2, "maxN":2, "help":"Play Velocity Table across - up/down."},
'v':{ "func":"vel_table_stop", "minN":0, "maxN":0, "help":"Stop the velocity table playback."},
'N':{ "func":"note_tester_start", "minN":0, "maxN":0, "help":"Play a note using NoteTester."},
'n':{ "func":"note_tester_stop", "minN":0, "maxN":0, "help":"Stop NoteTester."},
'T':{ "func":"poly_note_tester_start", "minN":0, "maxN":0, "help":"Play random notes using PolyNoteTester."},
't':{ "func":"poly_note_tester_stop", "minN":0, "maxN":0, "help":"Stop NoteTester."},
'H':{ "func":"chord_tester_start", "minN":0, "maxN":0, "help":"Chord tester start."},
'h':{ "func":"chord_tester_stop", "minN":0, "maxN":0, "help":"Chord tester stop."},
'P':{ "func":"pedal_down", "minN":0, "maxN":0, "help":"Pedal down."}, 'P':{ "func":"pedal_down", "minN":0, "maxN":0, "help":"Pedal down."},
'U':{ "func":"pedal_up", "minN":0, "maxN":0, "help":"Pedal up."}, 'p':{ "func":"pedal_up", "minN":0, "maxN":0, "help":"Pedal up."},
'O':{ "func":"all_notes_off", "minN":0, "maxN":0, "help":"All notes off."},
'E':{ "func":"check_for_errors", "minN":0, "maxN":0, "help":"Check for errors."}
} }
def _help( self, _=None ): def _help( self, _=None ):

361
p_ac.yml
View File

@ -5,6 +5,7 @@
# Audio device setup # Audio device setup
audio: { audio: {
inPortLabel: "8 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device", inPortLabel: "8 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
#inPortLabel: "8 Scarlett 18i20 USB: Audio",
outPortLabel: , outPortLabel: ,
}, },
@ -12,10 +13,60 @@
inMonitorFl: False, inMonitorFl: False,
outMonitorFl: False, outMonitorFl: False,
throughFl: False, throughFl: False,
inPortLabel: "Fastlane:Fastlane MIDI A", #inPortLabel: "MIDI9/QRS PNOScan:MIDI9/QRS PNOScan MIDI 1",
outPortLabel: "Fastlane:Fastlane MIDI A" #outPortLabel: "MIDI9/QRS PNOScan:MIDI9/QRS PNOScan MIDI 1"
#inPortLabel: "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"
},
NoteTester:
{
noteCount: 200,
minNoteDurMs: 100,
maxNoteDurMs: 1000,
minPauseDurMs: 5,
maxPauseDurMs: 500,
pitch: 60,
minAttackUsec: 4000,
maxAttackUsec: 20000,
filename: "note_tester.json"
},
PolyNoteTester:
{
mode: "simple",
noteCount: 11,
minPitch: 21,
maxPitch: 31,
skipPitchL: [22,68,102],
minNoteDurMs: 1000,
maxNoteDurMs: 2000,
minInterOnsetMs: 100,
maxInterOnsetMs: 1000,
minAttackUsec: 4000,
maxAttackUsec: 20000,
holdDutyPct: 35,
},
ChordTester:
{
useVelTableFl: False,
#pitchL: [24,36,48,60,72,84,96],
#pitchL: [24,96,48,72,36,84,60],
#pitchL: [ 36,40,43,47, 48, 52, 55, 59],
#pitchL: [ 36,39,41,33 ],
pitchL: [ 21,23,24,25,26,27,28,29,30,31],
#pitchL: [ 36,33,35,34,37,40,43,42,38,39,41],
#pitchL: [ 43,44,45,46,47,48,49,50,51,52,53],
#pitchL: [ 54,55,56,57,58,59,60,61,62,63,64 ],
repeatCnt: 3,
atkUsec: 10000,
durMs: 1000,
pauseMs: 1000,
holdDuty: 35,
}, },
# Picadae API args # Picadae API args
@ -28,11 +79,11 @@
# MeasureSeq args # MeasureSeq args
outDir: "~/temp/p_ac_3_of", outDir: "~/temp/p_ac_3_ok",
noteDurMs: 500, noteDurMs: 500,
pauseDurMs: 500, pauseDurMs: 500,
reversePulseListFl: True, reversePulseListFl: True,
useFullPulseListFl: True, useFullPulseListFl: True, # don't select a new set of resample points based on the previous samples from this pitch
maxSilentNoteCount: 4, maxSilentNoteCount: 4,
silentNoteMaxPulseUs: 15000, silentNoteMaxPulseUs: 15000,
silentNoteMinDurMs: 180, #250, silentNoteMinDurMs: 180, #250,
@ -40,7 +91,7 @@
defaultHoldDelayUsecs: 1000, defaultHoldDelayUsecs: 1000,
# Midi file player # Midi file player
midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt", midiFileFn: "/home/kevin/media/audio/midi/txt/988-v25.txt",
@ -94,7 +145,26 @@
full_pulse22L: [ 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000 ], full_pulse22L: [ 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000 ],
# 60,72,84 # 60,72,84
full_pulseL: [ 2000, 2250, 2500,2750, 3000, 3250, 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000 ], full_pulse23L: [ 2000, 2250, 2500,2750, 3000, 3250, 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000 ],
full_pulseL24: [ 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000 ],
# 23-40
full_pulseL25: [ 3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 ],
full_pulse25aL: [ 2000, 2050, 2100, 2150, 2200, 2250, 2300, 2350, 2400, 2450, 2500, 2550, 2600, 2650, 2700, 2750, 2800, 2850, 2900, 2950, 3000, 3050, 3100, 3150, 3200, 3250, 3300, 3350, 3400, 3450, 3500, 3550, 3600, 3650, 3700, 3750, 3800, 3850, 3900, 3950, 4000, 4050, 4100, 4150, 4200, 4250, 4300, 4350, 4400, 4450, 4500, 4550, 4600, 4650, 4700, 4750, 4800, 4850, 4900, 4950, 5000, 5050, 5100, 5150, 5200, 5250, 5300, 5350, 5400, 5450, 5500, 5550, 5600, 5650, 5700, 5750, 5800, 5850, 5900, 5950, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 ],
# 21-
full_pulse26L: [ 1500, 1625, 1750, 1875, 2000, 2125, 2250, 2375, 2500, 2625, 2750, 2875, 3000, 3125, 3250, 3375,3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000 ],
full_pulseL: [ 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000 ],
# 92
full_pulse27L: [ 1500, 1625, 1750, 1875, 2000, 2125, 2250, 2375, 2500, 2625, 2750, 2875, 3000, 3125, 3250, 3375, 3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000 ],
full_pulse28L: [ 4000, 8000, 12000, 16000 ],
full_pulse29L: [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 13500, 14000, 14500, 15000, 15500, 16000, 16500, 17000, 17500, 18000, 18500, 19000, 19500, 20000 ],
# RMS analysis args # RMS analysis args
analysisArgs: { analysisArgs: {
@ -112,28 +182,113 @@
resampleMinDurMs: 150, # notes's whose duration is less than this will be skipped resampleMinDurMs: 150, # notes's whose duration is less than this will be skipped
useLastTakeOnlyFl: True, useLastTakeOnlyFl: True,
minAttkDb: -5.0, # threshold of silence level minAttkDb: -5.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 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) maxDeltaDb: 1.5, # maximum db change between volume samples (changes greater than this will trigger resampling)
samplesPerDb: 4, # count of samples per dB to resample ranges whose range is less than maxDeltaDb samplesPerDb: 4, # count of samples per dB to resample ranges whose range is less than maxDeltaDb
minSampleDistUs: 50, # minimum distance between sample points in microseconds minSampleDistUs: 50, # minimum distance between sample points in microseconds
auditionNoteN: 19, # count of notes to play for audition auditionNoteN: 19, # count of notes to play for audition
finalPulseListCacheFn: "/home/kevin/temp/final_pulse_list_cache.pickle", finalPulseListCacheFn: "/home/kevin/temp/final_pulse_list_cache.pickle",
rmsAnalysisCacheFn: "/home/kevin/temp/rms_analysis_cache.pickle" rmsAnalysisCacheFn: "/home/kevin/temp/rms_analysis_cache.pickle"
}, },
manualMinD: { # used by plot_seq_1.gen_vel_map()
36: [2, 10], velTableDbL: [ 2, 7, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29 ],
48: [2, 10],
60: [2, 10],
72: [2, 10],
84: [2, 10]
},
manualAnchorPitchMinDbL: [ 36,48,72,84 ], manualMinD: {
manualAnchorPitchMaxDbL: [ 36,48,60,72,84 ], 21:[-1,6],
23:[-1,1], # 10
24:[-1,11], # 11
25:[-1,8],
26:[-1,9], # 13
27:[-1,11],
28:[-1,2],
29:[-1,16],
30:[-1,4],
31:[-1,8],
32:[-1,9],
33:[-1,10],
34:[-1,9], # 15
35:[-1,9],
36:[-1,4],
37:[-1,7],
38:[-1,5],
39:[-1,5],
40:[-1,6], # 2 lwr
41:[-1,10], # 24 lwr
42:[-1,2],
43:[-1,4],
44:[-1,5],
45:[-1,10],
46:[-1,4],
47:[-1,7],
48:[-1,9],
49:[-1,2],
50:[-1,5],
51:[-1,4],
52:[-1,8],
53:[-1,0], # 16
54:[-1,8],
55:[-1,14],
56:[-1,8],
57:[-1,9],
58:[-1,10],
59:[-1,7],
60:[-1,7], # 14
61:[-1,10], # 14
62:[-1,1], # 7
63:[-1,5],
64:[-1,2],
65:[-1,8],
66:[-1,3],
67:[-1,3], # 17 lwr
68:[-1,3],
69:[-1,9],
70:[-1,15], # 15 lwr
71:[-1,0], # 7 lwr
72:[-1,3],# 12 lwr
73:[-1,0],# 21 lwr
74:[-1,0],# 24 lwr
75:[-1,0],# 18 lwr
76:[-1,0],# 13 lwr
77:[-1,0],# 22 lwr
78:[-1,0],# 14 lwr
79:[-1,8],
80:[-1,3],
81:[-1,2], # 14 lwr
82:[-1,5], # 11
83:[-1,7],
84:[-1,3], # 13
85:[-1,5],
86:[-1,4],
87:[-1,7],
88:[-1,5], # 22
89:[-1,19],
90:[-1,2], # 10
91:[-1,10],
92:[-1,8],
93:[-1,7],
94:[-1,5], # 10
95:[-1,9],
96:[-1,13],
97:[-1,12],
98:[-1,14],
99:[-1,14],
100:[-1,18],
101:[-1,18],
103:[-1,25],
104:[-1,26],
105:[-1,18],
106:[-1,26],
107:[-1,29],
108:[-1,28],
},
manualAnchorPitchMinDbL: [ 24, 36,48,60,72,84,96 ],
manualAnchorPitchMaxDbL: [ 24, 36,48,60,72,84,96 ],
manualLastFl: true,
manualMinD_0: { manualMinD_0: {
23: [2, 24], 23: [2, 24],
@ -256,85 +411,93 @@
dbSrcLabel: 'hm', # 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, 40]], 21: [[0, 50]],
24: [[0, 40]], 23: [[0, 50]],
25: [[0, 40]], 24: [[0, 50]],
26: [[0, 40]], 25: [[0, 50]],
27: [[0, 40]], 26: [[0, 50]],
28: [[0, 40]], 27: [[0, 50]],
29: [[0, 40]], 28: [[0, 50]],
30: [[0, 40]], 29: [[0, 50]],
31: [[0, 40]], 30: [[0, 50]],
32: [[0, 40]], 31: [[0, 50]],
33: [[0, 40]], 32: [[0, 50]],
34: [[0, 40]], 33: [[0, 50]],
35: [[0, 40]], 34: [[0, 50]],
36: [[0, 40]], 35: [[0, 50]],
37: [[0, 40]], 36: [[0, 50]],
38: [[0, 40]], 37: [[0, 50]],
39: [[0, 40]], 38: [[0, 50]],
40: [[0, 40]], 39: [[0, 50]],
41: [[0, 40]], 40: [[0, 50]],
42: [[0, 40]], 41: [[0, 45]],
43: [[0, 40]], 42: [[0, 45]],
44: [[0, 40]], 43: [[0, 45]],
45: [[0, 40]], 44: [[0, 45]],
46: [[0, 40]], 45: [[0, 45]],
47: [[0, 40]], 46: [[0, 40],[10000,45]],
48: [[0, 40]], 47: [[0, 40],[10000,45]],
49: [[0, 40]], 48: [[0, 40],[10000,45]],
50: [[0, 40]], 49: [[0, 40],[10000,45]],
51: [[0, 40]], 50: [[0, 40],[10000,45]],
52: [[0, 40]], 51: [[0, 40],[10000,45]],
53: [[0, 40]], 52: [[0, 40],[10000,45]],
54: [[0, 40]], 53: [[0, 40],[10000,45]],
55: [[0, 40]], 54: [[0, 40],[10000,45]],
56: [[0, 40]], 55: [[0, 40],[10000,45]],
57: [[0, 40]], 56: [[0, 40],[10000,45]],
58: [[0, 40]], 57: [[0, 40],[10000,45]],
59: [[0, 45]], 58: [[0, 40],[10000,45]],
60: [[0, 40],[10000,50]], 59: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
61: [[0, 40],[10000,50]], 60: [[0, 40],[10000,43],[11000,45],[12000,45], [13000,50],[14000,55],[15000,60],[16000,65]],
62: [[0, 40],[10000,50]], 61: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
63: [[0, 40],[10000,50]], 62: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
64: [[0, 40],[10000,50]], 63: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
65: [[0, 99]], 64: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
66: [[0, 40]], 65: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
67: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]], 66: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
68: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]], 67: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
69: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]], 68: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
70: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]], 69: [[0, 32],[5000,64] ],
71: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]], 70: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
72: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 71: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
73: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 72: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
74: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 73: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
75: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 74: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
76: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 75: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
77: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 76: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
78: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 77: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
79: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 78: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
80: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 79: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
81: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 80: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
82: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 81: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
83: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 82: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
84: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 83: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
85: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ], 84: [[0, 35],[8000,40],[10000,45],[12000,48],[13000,50],[14000,55]],
86: [[0, 40]], 85: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
87: [[0, 40]], 86: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
88: [[0, 40]], 87: [[0, 40],[10000,45],[12000,48],[13000,50]],
89: [[0, 40]], 88: [[0, 40],[10000,45],[12000,48],[13000,50]],
91: [[0, 40]], 89: [[0, 40],[10000,45],[12000,48],[13000,50]],
92: [[0, 40]], 90: [[0, 40],[10000,45],[12000,48],[13000,50]],
93: [[0, 40]], 91: [[0, 40],[10000,45],[12000,48],[13000,50]],
94: [[0, 40]], 92: [[0, 37]],
95: [[0, 40]], 93: [[0, 37]],
96: [[0, 40]], 94: [[0, 37]],
95: [[0, 37]],
96: [[0, 37]],
97: [[0, 40]], 97: [[0, 40]],
98: [[0, 40]], 98: [[0, 40]],
99: [[0, 40]], 99: [[0, 37]],
100: [[0, 40]], 100: [[0, 37]],
101: [[0, 40]], 101: [[0, 37]],
106: [[0, 40]] 102: [[0, 37]],
103: [[0, 40],[10000,45],[15000,50]],
104: [[0, 40],[7500,45],[15000,50]],
105: [[0, 40],[10000,45],[15000,50]],
106: [[0, 37]],
107: [[0, 37]],
108: [[0, 37]],
}, },
# Final for Matt's piano # Final for Matt's piano

View File

@ -71,7 +71,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.5" "version": "3.8.6"
} }
}, },
"nbformat": 4, "nbformat": 4,

View File

@ -67,7 +67,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.5" "version": "3.8.6"
} }
}, },
"nbformat": 4, "nbformat": 4,

View File

@ -1,6 +1,6 @@
##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org> ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org>
##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
import os, sys,json import os, sys,json,math
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
@ -458,17 +458,43 @@ def _plot_us_db_takes( inDir, cfg, pitchL, takeIdL, printDir="", printFn="" ):
usL, dbL, durMsL, _, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'], takeId=takeId ) usL, dbL, durMsL, _, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'], takeId=takeId )
ax.plot(usL,dbL, marker='.',label="%i:%i %s %s" % (midi_pitch,takeId,keyMapD[midi_pitch]['class'],keyMapD[midi_pitch]['type'])) ax.plot(usL,dbL, marker='.',label="%i:%i %s %s" % (midi_pitch,takeId,keyMapD[midi_pitch]['class'],keyMapD[midi_pitch]['type']))
# for i,(x,y) in enumerate(zip(usL,dbL)): for i,(x,y) in enumerate(zip(usL,dbL)):
# ax.text(x,y,str(i)) ax.text(x,y,str(i))
f_usL,f_dbL = filter_us_db(usL,dbL)
ax.plot(f_usL,f_dbL, marker='.')
elbow_us,elbow_db = elbow.find_elbow(usL,dbL)
ax.plot([elbow_us],[elbow_db],marker='*',markersize=12,color='red',linestyle='None')
elb_idx = nearest_sample_point( dbL, usL, elbow_db, elbow_us )
if printDir: if printDir:
plt.savefig(os.path.join(printDir,printFn),format="png") plt.savefig(os.path.join(printDir,printFn),format="png")
plt.legend() plt.legend()
plt.show() plt.show()
def get_pitches_and_takes( inDir ):
pitchD = {}
inDirL = os.listdir( inDir )
for pitch in inDirL:
path = os.path.join( inDir, pitch )
takeIdL = os.listdir( path )
takeIdL = sorted([ int(takeId) for takeId in takeIdL ])
takeIdL = [ str(x) for x in takeIdL ]
pitchD[int(pitch)] = takeIdL
return pitchD
def plot_us_db_takes( inDir, cfg, pitchL, printDir=""): def plot_us_db_takes( inDir, cfg, pitchL, printDir=""):
@ -480,14 +506,11 @@ def plot_us_db_takes( inDir, cfg, pitchL, printDir=""):
def plot_us_db_takes_last( inDir, cfg, pitchL, printDir ): def plot_us_db_takes_last( inDir, cfg, pitchL, printDir ):
pitchD = get_pitches_and_takes(inDir)
takeIdL = [] takeIdL = []
for pitch in pitchL: for pitch in pitchL:
takeIdL.append( int(pitchD[pitch][-1]) )
inDirL = os.listdir( os.path.join(inDir,str(pitch)))
inDirL = sorted(inDirL)
takeIdL.append( int(inDirL[-1]) )
return _plot_us_db_takes( inDir, cfg, pitchL, takeIdL, printDir, "us_db_takes_last.png") return _plot_us_db_takes( inDir, cfg, pitchL, takeIdL, printDir, "us_db_takes_last.png")
@ -535,17 +558,21 @@ def plot_all_noise_curves( inDir, cfg, pitchL=None ):
plt.legend() plt.legend()
plt.show() plt.show()
def nearest_sample_point( dbL, usL, db0, us0 ):
xL = np.array([ abs(us-us0) for db,us in zip(dbL,usL) ])
return np.argmin(xL)
def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ): def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
takeIdArg = takeId
pitchTakeD = get_pitches_and_takes(inDir)
pitchFolderL = os.listdir(inDir) pitchFolderL = os.listdir(inDir)
print(pitchL)
if pitchL is None: if pitchL is None:
pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ] pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
print(pitchL)
okL = [] okL = []
outPitchL = [] outPitchL = []
minDbL = [] minDbL = []
@ -553,9 +580,11 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
for midi_pitch in pitchL: for midi_pitch in pitchL:
print(midi_pitch) takeId = None
if takeIdArg == -1:
takeId = pitchTakeD[midi_pitch][-1]
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] ) usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'], takeId )
okL.append(False) okL.append(False)
@ -571,6 +600,9 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
minDbL.append(elbow_db) minDbL.append(elbow_db)
outPitchL.append(midi_pitch) outPitchL.append(midi_pitch)
smp_idx = nearest_sample_point( dbL, usL, elbow_db, elbow_us )
print(" %i:[-1,%i], " % (midi_pitch,smp_idx))
p_dL = sorted( zip(outPitchL,minDbL,maxDbL,okL), key=lambda x: x[0] ) p_dL = sorted( zip(outPitchL,minDbL,maxDbL,okL), key=lambda x: x[0] )
@ -593,7 +625,9 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
plt.show() plt.show()
def plot_min_db_manual( inDir, cfg, printDir=None ): def plot_min_db_manual( inDir, cfg, printDir=None, absMaxDb=27, absMinDb=3 ):
pitchTakeD = get_pitches_and_takes(inDir)
pitchL = list(cfg.manualMinD.keys()) pitchL = list(cfg.manualMinD.keys())
@ -606,30 +640,40 @@ def plot_min_db_manual( inDir, cfg, printDir=None ):
for midi_pitch in pitchL: for midi_pitch in pitchL:
manual_take_id = cfg.manualMinD[midi_pitch][0] if cfg.manualLastFl:
manual_take_id = pitchTakeD[midi_pitch][-1]
takeId = manual_take_id
else:
manual_take_id = cfg.manualMinD[midi_pitch][0]
takeId = None
manual_sample_idx = cfg.manualMinD[midi_pitch][1] manual_sample_idx = cfg.manualMinD[midi_pitch][1]
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] ) usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'], takeId )
okL.append(False) okL.append(False)
takeId = len(set(takeIdL))-1 if takeId is None:
takeId = len(set(takeIdL))-1
# most pitches have 3 sample takes that do not
if len(set(takeIdL)) == 3 and manual_take_id == takeId:
okL[-1] = True
else:
okL[-1] = True
# maxDb is computed on all takes (not just the specified take) # maxDb is computed on all takes (not just the specified take)
db_maxL = sorted(dbL) db_maxL = sorted(dbL)
max_db = np.mean(db_maxL[-4:]) max_db = min(absMaxDb,np.mean(db_maxL[-4:]))
maxDbL.append( max_db ) maxDbL.append( max_db )
# get the us,db values for the specified take # 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 ]) 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 # min db from the sample index manually specified in cfg
manualMinDb = dbL[ manual_sample_idx ] manualMinDb = max(absMinDb,dbL[ manual_sample_idx ])
minDbL.append( manualMinDb ) minDbL.append( manualMinDb )
outPitchL.append(midi_pitch) outPitchL.append(midi_pitch)
@ -642,8 +686,6 @@ def plot_min_db_manual( inDir, cfg, printDir=None ):
# Form the complete set of min/max db levels for each pitch by interpolating the # Form the complete set of min/max db levels for each pitch by interpolating the
# db values between the manually selected anchor points. # db values between the manually selected anchor points.
interpMinDbL = np.interp( pitchL, cfg.manualAnchorPitchMinDbL, anchorMinDbL ) interpMinDbL = np.interp( pitchL, cfg.manualAnchorPitchMinDbL, anchorMinDbL )
@ -861,27 +903,67 @@ def report_take_ids( inDir ):
if len(takeDirL) == 0: if len(takeDirL) == 0:
print(pitch," directory empty") print(pitch," directory empty")
else: else:
with open( os.path.join(pitchDir,'0','seq.json'), "rb") as f:
r = json.load(f) fn = os.path.join(pitchDir,'0','seq.json')
if not os.path.isfile(fn):
print("Missing sequence file:",fn)
else:
with open( fn, "rb") as f:
r = json.load(f)
if len(r['eventTimeL']) != 81:
print(pitch," ",len(r['eventTimeL'])) if len(r['eventTimeL']) != 81:
print(pitch," ",len(r['eventTimeL']))
if len(takeDirL) != 3: if len(takeDirL) != 3:
print("***",pitch,len(takeDirL)) print("***",pitch,len(takeDirL))
def filter_us_db( us0L, db0L ):
us1L = [us0L[-1]]
db1L = [db0L[-1]]
dDb = 0
lastIdx = 0
for i,(us,db) in enumerate(zip( us0L[::-1],db0L[::-1])):
db1 = db1L[-1]
if db < db1 and db1-db >= dDb/2:
dDb = db1 - db
us1L.append(us)
db1L.append(db)
lastIdx = i
lastIdx = len(us0L) - lastIdx - 1
usL = [ us0L[lastIdx] ]
dbL = [ db0L[lastIdx] ]
dDb = 0
for us,db in zip(us0L[lastIdx::],db0L[lastIdx::]):
db1 = dbL[-1]
if db > db1:
dDb = db-db1
usL.append(us)
dbL.append(db)
return usL,dbL
def cache_us_db( inDir, cfg, outFn ): def cache_us_db( inDir, cfg, outFn ):
pitchTakeD = get_pitches_and_takes(inDir)
pitch_usDbD = {} pitch_usDbD = {}
pitchDirL = os.listdir(inDir) pitchDirL = os.listdir(inDir)
for pitch in pitchDirL: for pitch,takeIdL in pitchTakeD.items():
pitch = int(pitch) pitch = int(pitch)
takeId = takeIdL[-1]
print(pitch) print(pitch)
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'] ) usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'], takeId )
pitch_usDbD[pitch] = { 'usL':usL, 'dbL':dbL, 'durMsL':durMsL, 'takeIdL':takeIdL, 'holdDutyPctL': holdDutyPctL } pitch_usDbD[pitch] = { 'usL':usL, 'dbL':dbL, 'durMsL':durMsL, 'takeIdL':takeIdL, 'holdDutyPctL': holdDutyPctL }
@ -889,7 +971,7 @@ def cache_us_db( inDir, cfg, outFn ):
with open(outFn,"w") as f: with open(outFn,"w") as f:
json.dump(pitch_usDbD,f) json.dump(pitch_usDbD,f)
def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ): def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
@ -897,28 +979,44 @@ def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
pitchDirL = os.listdir(inDir) pitchDirL = os.listdir(inDir)
# pitchUsDbD = { pitch:
with open(cacheFn,"r") as f: with open(cacheFn,"r") as f:
pitchUsDbD = json.load(f) pitchUsDbD = json.load(f)
# form minMaxDb = { pitch:(minDb,maxDb) }
with open("minInterpDb.json","r") as f: with open("minInterpDb.json","r") as f:
r = json.load(f) r = json.load(f)
minMaxDbD = { pitch:(minDb,maxDb) for pitch,minDb,maxDb in zip(r['pitchL'],r['minDbL'],r['maxDbL']) } 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'] pitchL = sorted( [ int(pitch) for pitch in pitchUsDbD.keys()] )
dbL = np.array(d['dbL'])
# for each pitch
for pitch in pitchL:
# get the us/db map for this
d = pitchUsDbD[str(pitch)]
usL, dbL = filter_us_db( d['usL'], d['dbL'] )
#usL = d['usL']
#dbL = np.array(d['dbL'])
dbL = np.array(dbL)
velMapD[pitch] = [] velMapD[pitch] = []
for i in range(dynLevelN+1):
db = minMaxDbD[pitch][0] + (i * (minMaxDbD[pitch][1] - minMaxDbD[pitch][0])/ dynLevelN) maxDb = minMaxDbD[pitch][1]
minDb = minMaxDbD[pitch][0]
dynLevelN = len(cfg.velTableDbL)
# for each dynamic level
for i in range(dynLevelN):
#db = minDb + (i * (maxDb - minDb)/ dynLevelN)
db = cfg.velTableDbL[i]
usIdx = np.argmin( np.abs(dbL - db) ) usIdx = np.argmin( np.abs(dbL - db) )
@ -946,7 +1044,7 @@ def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
if __name__ == "__main__": if __name__ == "__main__":
printDir = None #os.path.expanduser( "~/src/picadae_ac_3/doc") printDir = os.path.expanduser("~/temp") # os.path.expanduser( "~/src/picadae_ac_3/doc")
cfgFn = sys.argv[1] cfgFn = sys.argv[1]
inDir = sys.argv[2] inDir = sys.argv[2]
mode = sys.argv[3] mode = sys.argv[3]
@ -981,7 +1079,7 @@ if __name__ == "__main__":
elif mode == 'manual_db': elif mode == 'manual_db':
plot_min_db_manual( inDir, cfg, printDir=printDir ) plot_min_db_manual( inDir, cfg, printDir=printDir )
elif mode == 'gen_vel_map': elif mode == 'gen_vel_map':
gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" ) gen_vel_map( inDir, cfg, "minInterpDb.json", 12, "cache_us_db.json" )
elif mode == 'cache_us_db': elif mode == 'cache_us_db':
cache_us_db( inDir, cfg, "cache_us_db.json") cache_us_db( inDir, cfg, "cache_us_db.json")
else: else:

View File

@ -75,7 +75,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.5" "version": "3.8.6"
} }
}, },
"nbformat": 4, "nbformat": 4,

View File

@ -254,7 +254,7 @@ def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
def locate_peak_indexes( xV, xV_srate, eventMsL, audioFn ): def locate_peak_indexes( xV, xV_srate, eventMsL, audioFn="" ):
pkIdxL = [] pkIdxL = []
for i, (begMs, endMs) in enumerate(eventMsL): for i, (begMs, endMs) in enumerate(eventMsL):
@ -601,6 +601,9 @@ def write_audacity_label_files( inDir, analysisArgsD, reverseFl=True ):
takeDir = os.path.join(pitchDir,takeFolder) takeDir = os.path.join(pitchDir,takeFolder)
if not os.path.isfile(os.path.join(takeDir,"seq.json")):
continue
r = rms_analysis_main( takeDir, midi_pitch, **analysisArgsD ) r = rms_analysis_main( takeDir, midi_pitch, **analysisArgsD )
labelFn = os.path.join(takeDir,"audacity.txt") labelFn = os.path.join(takeDir,"audacity.txt")