Compare commits
9 Commits
42e8ebf1d2
...
ee3c41fe70
Author | SHA1 | Date | |
---|---|---|---|
|
ee3c41fe70 | ||
|
c017cafe51 | ||
|
a163756f04 | ||
|
9ecb4d41b4 | ||
|
349a6be063 | ||
|
38783413b6 | ||
|
1254547ce0 | ||
|
9b998596b7 | ||
|
49ef663176 |
@ -18,7 +18,7 @@ class AudioDevice(object):
|
||||
self.bufL = []
|
||||
self.bufIdx = -1
|
||||
self.srate = 0
|
||||
self.ch_cnt = 1
|
||||
self.ch_cnt = 2
|
||||
|
||||
|
||||
def setup( self, **kwargs ):
|
||||
|
82
README.md
@ -1,2 +1,84 @@
|
||||
* Picadae Calibration Programs
|
||||
|
||||
sudo dnf install python3-devel jack-audio-connection-kit-devel
|
||||
|
||||
|
||||
prerequisites:
|
||||
pyyaml
|
||||
sounddevice
|
||||
soundfile
|
||||
pthon-rtmidi
|
||||
|
||||
export PYTHONPATH=${HOME}/src/picadae/control/app
|
||||
python p_ac.py -c p_ac.yml
|
||||
|
||||
c 60 60 # caputre note 60 using the full_pulseL[] from the p_ac.yml
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# plot third take of note 60
|
||||
python plot_seq.py ~/temp/p_ac_3_oa/60 p_ac.yml 3
|
||||
|
||||
# record a calibration take: CalibrateKeys.start
|
||||
uses cfg.full_pulseL[] for the pulse sequence
|
||||
use cfg.calibrateArgs.holdDutyPctD{} for hold PWM duty cycle
|
||||
|
||||
# obsolete plotting function in calibrate_plot.py
|
||||
python calibrate_plot.py ~/temp/p_ac_3_oa/60/2 p_ac.yml 60
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
![Plot Seq 1](doc/do_td_plot.png)
|
||||
`python plot_seq.py ~/temp/p_ac_3_od p_ac.yml 60 10`
|
||||
`do_td_plot(inDir,cfg.analysisArgs, pitch, take_id )`
|
||||
|
||||
![Multi Plot 1](doc/multi_plot.png)
|
||||
`python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 60 3 60 4 60 5`
|
||||
plot_seq.py `do_td_multi_plot(inDir,cfg.analysisArgs,[(36,4), (48,2)] )`
|
||||
|
||||
![Spectral Ranges](doc/plot_spectral_ranges.png)
|
||||
`python plot_seq.py p_ac.yml ~/temp/p_ac_3_od plot_spectral_ranges 60 3 60 4`
|
||||
|
||||
|
||||
![Multi Usec dB](doc/us_db.png)
|
||||
`python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db 84`
|
||||
|
||||
![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`
|
||||
|
||||
![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`
|
||||
|
||||
|
||||
![Min Max 2](doc/min_max_db_2.png)
|
||||
The last number in the list is the '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 manual_db`
|
||||
|
||||
Interpolate across the min and max db values to form the min/max curves for the complete
|
||||
set of keys. The anchor points for the curves are taken from cfg record
|
||||
manuMinD,manualAnchorPitchMinDbL, and manualMaxDbL.
|
||||
|
||||
````
|
||||
# select the event (takeId, eventIdx) to use to represent the min value for each pitch
|
||||
manualMinD: {
|
||||
36: [2, 10],
|
||||
48: [2, 10],
|
||||
60: [2, 10],
|
||||
72: [2, 10],
|
||||
84: [2, 10]
|
||||
},
|
||||
|
||||
# leave 60 out of the min anchor point list
|
||||
manualAnchorPitchMinDbL: [ 36,48,72,84 ],
|
||||
|
||||
manualAnchorPitchMaxDbL: [ 36,48,60,72,84 ],
|
||||
|
||||
````
|
BIN
do_td_plot.png
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
doc/do_td_plot.png
Normal file
After Width: | Height: | Size: 170 KiB |
BIN
doc/manual_db.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
doc/min_max_db_2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
doc/multi_plot.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
doc/plot_spectral_ranges.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
doc/us_db_map.png
Normal file
After Width: | Height: | Size: 46 KiB |
156
p_ac.yml
@ -3,8 +3,8 @@
|
||||
|
||||
|
||||
# Audio device setup
|
||||
audio_off: {
|
||||
inPortLabel: "5 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
|
||||
audio: {
|
||||
inPortLabel: "8 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
|
||||
outPortLabel: ,
|
||||
},
|
||||
|
||||
@ -12,10 +12,10 @@
|
||||
inMonitorFl: False,
|
||||
outMonitorFl: False,
|
||||
throughFl: False,
|
||||
#inPortLabel: "Fastlane:Fastlane MIDI A",
|
||||
#outPortLabel: "Fastlane:Fastlane MIDI A"
|
||||
inPortLabel: "picadae:picadae MIDI 1",
|
||||
outPortLabel: "picadae:picadae MIDI 1"
|
||||
inPortLabel: "Fastlane:Fastlane MIDI A",
|
||||
outPortLabel: "Fastlane:Fastlane MIDI A"
|
||||
#inPortLabel: "picadae:picadae MIDI 1",
|
||||
#outPortLabel: "picadae:picadae MIDI 1"
|
||||
},
|
||||
|
||||
# Picadae API args
|
||||
@ -23,18 +23,19 @@
|
||||
serial_baud: 38400,
|
||||
i2c_base_addr: 21,
|
||||
prescaler_usec: 16,
|
||||
pwm_div: 5,
|
||||
serial_sync_timeout_ms: 10000,
|
||||
|
||||
|
||||
# MeasureSeq args
|
||||
outDir: "~/temp/p_ac_3g",
|
||||
outDir: "~/temp/p_ac_3_oe",
|
||||
noteDurMs: 500,
|
||||
pauseDurMs: 500,
|
||||
reversePulseListFl: True,
|
||||
useFullPulseListFl: True,
|
||||
maxSilentNoteCount: 4,
|
||||
silentNoteMaxPulseUs: 15000,
|
||||
silentNoteMinDurMs: 250,
|
||||
silentNoteMinDurMs: 180, #250,
|
||||
|
||||
# Midi file player
|
||||
midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt",
|
||||
@ -57,8 +58,8 @@
|
||||
|
||||
full_pulse8L: [ 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
|
||||
|
||||
|
||||
full_pulseL: [11000, 11075, 11150, 11225, 11300, 11375, 11450, 11525, 11600,11675, 11750, 11825, 11900, 11975, 12050, 12125, 12200, 12275,12350, 12425, 12500, 12575, 12650, 12725, 12800, 12875, 12950, 13025, 13100, 13175, 13250, 13325, 13400, 13475, 13550, 13625, 13700, 13775, 13850, 13925, 14000, 14075, 14150, 14225, 14300, 14375, 14450, 14525, 14600, 14675, 14750, 14825, 14900, 14975],
|
||||
# full_pulse9L was the last pulse list used on Matt's piano
|
||||
full_pulse9L: [11000, 11075, 11150, 11225, 11300, 11375, 11450, 11525, 11600,11675, 11750, 11825, 11900, 11975, 12050, 12125, 12200, 12275,12350, 12425, 12500, 12575, 12650, 12725, 12800, 12875, 12950, 13025, 13100, 13175, 13250, 13325, 13400, 13475, 13550, 13625, 13700, 13775, 13850, 13925, 14000, 14075, 14150, 14225, 14300, 14375, 14450, 14525, 14600, 14675, 14750, 14825, 14900, 14975],
|
||||
|
||||
full_pulse10L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
|
||||
|
||||
@ -66,6 +67,35 @@
|
||||
|
||||
full_pulse12L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000 ],
|
||||
|
||||
# pulse lists below this line were developed on Steinway D (11/14/20)
|
||||
# 60
|
||||
full_pulse13L: [8000, 8250, 8500, 8750, 9000, 9250, 9500, 9750, 10000, 10250, 10500, 10750, 11000, 11250, 11500, 11750, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500, 16000, 16500, 17000, 17500, 18000, 18500, 19000, 19500, 20000 ],
|
||||
|
||||
full_pulse14L: [ 9000, 9125, 9250, 9375, 9500, 9625, 9750, 9875, 10000, 10125, 10250, 10375, 10500, 10625, 10750, 10875, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500, 16000, 16500, 17000, 17500, 18000, 18500, 19000, 19500, 20000, 20500, 21000, 21500, 22000 ],
|
||||
|
||||
full_pulse15L: [ 8000, 9000,10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 ],
|
||||
|
||||
full_pulse16L: [ 8000, 8125, 8250, 8375, 8500, 8625, 8750, 8875, 9000, 9125, 9250, 9375, 9500, 9625, 9750, 9875, 10000, 10125, 10250, 10375, 10500, 10625, 10750, 10875, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500, 16000, 16500, 17000, 17500, 18000, 18500, 19000, 19500, 20000, 20500, 21000, 21500, 22000 ],
|
||||
|
||||
full_pulse17L: [ 8100, 8150, 8200, 8250, 8300, 8350, 8400, 8450, 8500, 8550, 8600, 8650, 8700, 8750, 8800, 8850, 8900, 8950, 9000, 9125, 9250, 9375, 9500, 9625, 9750, 9875, 10000, 10125, 10250, 10375, 10500, 10625, 10750, 10875, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500, 16000, 16500, 17000, 17500, 18000, 18500, 19000, 19500, 20000, 20500, 21000, 21500, 22000 ],
|
||||
|
||||
full_pulse18L: [ 11500, 11625, 11750, 11875, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500 ],
|
||||
|
||||
full_pulse19L: [ 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13250, 13500, 13750, 14000, 14500, 15000, 15500, 16000, 16500 ],
|
||||
|
||||
# pulse lists below this line were developed on Steinway D w/ new firmware (11/21/20)
|
||||
|
||||
full_pulse20L: [ 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3250, 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 8750, 9000, 9250, 9500, 9750, 10000, 10250, 10500, 10750, 11000, 11250, 11500, 11750, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500 ],
|
||||
|
||||
# 60,72
|
||||
full_pulse21L: [ 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3250, 3500, 3750, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000 ],
|
||||
|
||||
# 48,36,85
|
||||
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 ],
|
||||
|
||||
# 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, 12000, 12500, 13000, 14000, 15000, 16000 ],
|
||||
|
||||
# RMS analysis args
|
||||
analysisArgs: {
|
||||
rmsAnalysisArgs: {
|
||||
@ -77,11 +107,12 @@
|
||||
durDecayPct: 40, # percent drop in RMS to indicate the end of a note
|
||||
},
|
||||
|
||||
resampleMinDb: 7.0, # note's less than this will be skipped
|
||||
resampleMinDb: -5.0, # note's less than this will be skipped
|
||||
resampleNoiseLimitPct: 5.0, #
|
||||
resampleMinDurMs: 800, # notes's whose duration is less than this will be skipped
|
||||
resampleMinDurMs: 150, # notes's whose duration is less than this will be skipped
|
||||
|
||||
minAttkDb: 7.0, # threshold of silence level
|
||||
useLastTakeOnlyFl: True,
|
||||
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
|
||||
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
|
||||
@ -93,6 +124,18 @@
|
||||
},
|
||||
|
||||
manualMinD: {
|
||||
36: [2, 10],
|
||||
48: [2, 10],
|
||||
60: [2, 10],
|
||||
72: [2, 10],
|
||||
84: [2, 10]
|
||||
},
|
||||
|
||||
manualAnchorPitchMinDbL: [ 36,48,72,84 ],
|
||||
manualAnchorPitchMaxDbL: [ 36,48,60,72,84 ],
|
||||
|
||||
|
||||
manualMinD_0: {
|
||||
23: [2, 24],
|
||||
24: [2, 18],
|
||||
25: [2, 41],
|
||||
@ -175,8 +218,8 @@
|
||||
|
||||
},
|
||||
|
||||
manualAnchorPitchMinDbL: [ 23, 27, 31, 34, 44, 51, 61, 70, 74, 81, 87, 93, 96, 101 ],
|
||||
manualAnchorPitchMaxDbL: [ 23, 32, 49, 57, 67, 76, 83, 93, 99, 101 ],
|
||||
manualAnchorPitchMinDbL_0: [ 23, 27, 31, 34, 44, 51, 61, 70, 74, 81, 87, 93, 96, 101 ],
|
||||
manualAnchorPitchMaxDbL_0: [ 23, 32, 49, 57, 67, 76, 83, 93, 99, 101 ],
|
||||
|
||||
calibrateArgs: {
|
||||
|
||||
@ -213,6 +256,89 @@
|
||||
dbSrcLabel: 'hm', # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
|
||||
|
||||
holdDutyPctD: {
|
||||
23: [[0, 40]],
|
||||
24: [[0, 40]],
|
||||
25: [[0, 40]],
|
||||
26: [[0, 40]],
|
||||
27: [[0, 40]],
|
||||
28: [[0, 40]],
|
||||
29: [[0, 40]],
|
||||
30: [[0, 40]],
|
||||
31: [[0, 40]],
|
||||
32: [[0, 40]],
|
||||
33: [[0, 40]],
|
||||
34: [[0, 40]],
|
||||
35: [[0, 40]],
|
||||
36: [[0, 45]],
|
||||
37: [[0, 40]],
|
||||
38: [[0, 40]],
|
||||
39: [[0, 40]],
|
||||
40: [[0, 40]],
|
||||
41: [[0, 40]],
|
||||
42: [[0, 40]],
|
||||
43: [[0, 40]],
|
||||
44: [[0, 40]],
|
||||
45: [[0, 40]],
|
||||
46: [[0, 40]],
|
||||
47: [[0, 40]],
|
||||
48: [[0, 40]],
|
||||
49: [[0, 40]],
|
||||
50: [[0, 40]],
|
||||
51: [[0, 40]],
|
||||
52: [[0, 40]],
|
||||
53: [[0, 40]],
|
||||
54: [[0, 40]],
|
||||
55: [[0, 40]],
|
||||
56: [[0, 40]],
|
||||
57: [[0, 40]],
|
||||
58: [[0, 40]],
|
||||
59: [[0, 40]],
|
||||
60: [[0, 50]],
|
||||
61: [[0, 43]],
|
||||
62: [[0, 43]],
|
||||
63: [[0, 43]],
|
||||
64: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
65: [[0, 99]],
|
||||
66: [[0, 40]],
|
||||
67: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
68: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
69: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
70: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
71: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
72: [[0, 45],[11000, 65] ],
|
||||
73: [[0, 45],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
74: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
75: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
76: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
77: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
78: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
79: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
80: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
81: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
82: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
|
||||
83: [[0, 42]],
|
||||
84: [[0, 42],[10000,50],[11000,60]],
|
||||
85: [[0, 42],[9000,45],[10000,50],[11000,60]],
|
||||
86: [[0, 40]],
|
||||
87: [[0, 40]],
|
||||
88: [[0, 40]],
|
||||
89: [[0, 40]],
|
||||
91: [[0, 40]],
|
||||
92: [[0, 40]],
|
||||
93: [[0, 40]],
|
||||
94: [[0, 40]],
|
||||
95: [[0, 40]],
|
||||
96: [[0, 40]],
|
||||
97: [[0, 40]],
|
||||
98: [[0, 40]],
|
||||
99: [[0, 40]],
|
||||
100: [[0, 40]],
|
||||
101: [[0, 40]],
|
||||
106: [[0, 40]]
|
||||
},
|
||||
|
||||
# Final for Matt's piano
|
||||
holdDutyPct0D: {
|
||||
23: [[0, 70]],
|
||||
24: [[0, 75]],
|
||||
25: [[0, 70]],
|
||||
|
188
plot_seq.py
@ -1,14 +1,22 @@
|
||||
##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org>
|
||||
##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
||||
import os, sys
|
||||
import os, sys,json
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy.io import wavfile
|
||||
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
|
||||
|
||||
import rms_analysis as ra
|
||||
|
||||
#from rms_analysis import audio_rms
|
||||
#from rms_analysis import locate_peak_indexes
|
||||
#from rms_analysis import audio_stft_rms
|
||||
#from rms_analysis import calc_harm_bins
|
||||
|
||||
def is_nanV( xV ):
|
||||
|
||||
for i in range(xV.shape[0]):
|
||||
@ -31,13 +39,16 @@ def _find_max_take_id( inDir ):
|
||||
|
||||
def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
|
||||
|
||||
|
||||
# append the midi pitch to the input directory
|
||||
#inDir = os.path.join( inDir, "%i" % (midi_pitch))
|
||||
inDir = os.path.join( inDir, str(midi_pitch))
|
||||
|
||||
dirL = os.listdir(inDir)
|
||||
|
||||
pkL = []
|
||||
|
||||
maxTakeNumber = 0
|
||||
|
||||
# for each take in this directory
|
||||
for idir in dirL:
|
||||
|
||||
@ -47,6 +58,14 @@ def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
|
||||
if not os.path.isfile(os.path.join( inDir,idir, "seq.json")):
|
||||
continue
|
||||
|
||||
if analysisArgsD['useLastTakeOnlyFl']:
|
||||
if take_number > maxTakeNumber:
|
||||
pkL.clear()
|
||||
maxTakeNumber = take_number
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
# analyze this takes audio and locate the note peaks
|
||||
r = rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
|
||||
|
||||
@ -68,6 +87,8 @@ def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
|
||||
# locate the first and last note
|
||||
min_pk_idx, max_pk_idx = find_min_max_peak_index( pkDbL, analysisArgsD['minAttkDb'], analysisArgsD['maxDbOffset'] )
|
||||
|
||||
print("MIN MAX:",min_pk_idx,pkUsL[min_pk_idx],max_pk_idx,pkUsL[max_pk_idx])
|
||||
|
||||
db1 = pkDbL[ max_pk_idx ]
|
||||
db0 = pkDbL[ min_pk_idx ]
|
||||
|
||||
@ -195,14 +216,12 @@ def calc_resample_ranges( pkDbL, pkUsL, min_pk_idx, max_pk_idx, maxDeltaDb, samp
|
||||
|
||||
|
||||
|
||||
def form_resample_pulse_time_list( inDir, analysisArgsD ):
|
||||
def form_resample_pulse_time_list( inDir, midi_pitch, analysisArgsD ):
|
||||
"""" This function merges all available data from previous takes to form
|
||||
a new list of pulse times to sample.
|
||||
"""
|
||||
|
||||
# the last folder is always the midi pitch of the note under analysis
|
||||
midi_pitch = int( inDir.split("/")[-1] )
|
||||
|
||||
inDir = os.path.join( inDir, str(midi_pitch) )
|
||||
dirL = os.listdir(inDir)
|
||||
|
||||
pkL = []
|
||||
@ -248,11 +267,10 @@ def plot_curve( ax, pulseUsL, rmsDbV ):
|
||||
|
||||
ax.plot( pulseUsL, func(pulseUsL), color='red')
|
||||
|
||||
def plot_resample_pulse_times_0( inDir, analysisArgsD ):
|
||||
def plot_resample_pulse_times_0( inDir, analysisArgsD, midi_pitch, printDir="" ):
|
||||
|
||||
newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
|
||||
newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, midi_pitch, analysisArgsD )
|
||||
|
||||
midi_pitch = int( inDir.split("/")[-1] )
|
||||
velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
|
||||
|
||||
fig,ax = plt.subplots()
|
||||
@ -262,16 +280,18 @@ def plot_resample_pulse_times_0( inDir, analysisArgsD ):
|
||||
for us in newPulseUsL:
|
||||
ax.axvline( x = us )
|
||||
|
||||
ax.plot(velTblUsL,velTblDbL,marker='.',linestyle='None')
|
||||
print(len(velTblUsL))
|
||||
ax.plot(velTblUsL,velTblDbL,marker='.',linestyle='None',color='red')
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"plot_resample_times_0.png"),format="png")
|
||||
|
||||
plt.show()
|
||||
|
||||
def plot_resample_pulse_times( inDir, analysisArgsD ):
|
||||
def plot_resample_pulse_times( inDir, analysisArgsD, midi_pitch, printDir="" ):
|
||||
|
||||
newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
|
||||
newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, midi_pitch, 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]})
|
||||
@ -290,6 +310,10 @@ def plot_resample_pulse_times( inDir, analysisArgsD ):
|
||||
axL[1].axhline(1.0,color='black')
|
||||
axL[1].plot(pulseUsL,np.abs(scoreV * 100.0 / rmsDbV))
|
||||
axL[1].set_ylim((0.0,50))
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"plot_resample_times.png"),format="png")
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
@ -369,7 +393,7 @@ def plot_spectrum( ax, srate, binHz, specV, midiPitch, harmN ):
|
||||
""" Plot a single spectrum, 'specV' and the harmonic peak location boundaries."""
|
||||
|
||||
binN = specV.shape[0]
|
||||
harmLBinL,harmMBinL,harmUBinL = calc_harm_bins( srate, binHz, midiPitch, harmN )
|
||||
harmLBinL,harmMBinL,harmUBinL = ra.calc_harm_bins( srate, binHz, midiPitch, harmN )
|
||||
|
||||
fundHz = harmMBinL[0] * binHz
|
||||
maxPlotHz = fundHz * (harmN+1)
|
||||
@ -386,19 +410,19 @@ def plot_spectrum( ax, srate, binHz, specV, midiPitch, harmN ):
|
||||
ax.axvline( x=h1 * binHz, color="black")
|
||||
ax.axvline( x=h2 * binHz, color="blue")
|
||||
|
||||
ax.set_ylabel(str(midiPitch))
|
||||
ax.set_ylabel("dB : %i " % (midiPitch))
|
||||
|
||||
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."""
|
||||
def plot_spectral_ranges( inDir, pitchTakeL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbLinRef=0.001, printDir="" ):
|
||||
""" Plot the spectrum from one note (7th from last) in each attack pulse length sequence referred to by pitchTakeL."""
|
||||
|
||||
plotN = len(pitchL)
|
||||
plotN = len(pitchTakeL)
|
||||
fig,axL = plt.subplots(plotN,1)
|
||||
|
||||
for plot_idx,midiPitch in enumerate(pitchL):
|
||||
for plot_idx,(midiPitch,takeId) in enumerate(pitchTakeL):
|
||||
|
||||
# get the audio and meta-data file names
|
||||
seqFn = os.path.join( inDir, str(midiPitch), "seq.json")
|
||||
audioFn = os.path.join( inDir, str(midiPitch), "audio.wav")
|
||||
seqFn = os.path.join( inDir, str(midiPitch), str(takeId), "seq.json")
|
||||
audioFn = os.path.join( inDir, str(midiPitch), str(takeId), "audio.wav")
|
||||
|
||||
# read the meta data object
|
||||
with open( seqFn, "rb") as f:
|
||||
@ -406,13 +430,20 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbL
|
||||
|
||||
# read the audio file
|
||||
srate, signalM = wavfile.read(audioFn)
|
||||
|
||||
|
||||
# convert the audio signal vector to contain only the first (left) channel
|
||||
if len(signalM.shape)>1:
|
||||
signalM = signalM[:,0].squeeze()
|
||||
|
||||
sigV = signalM / float(0x7fff)
|
||||
|
||||
|
||||
# calc. the RMS envelope in the time domain
|
||||
rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
|
||||
rms0DbV, rms0_srate = ra.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'] )
|
||||
pkIdx0L = ra.locate_peak_indexes( rms0DbV, rms0_srate, r['eventTimeL'] )
|
||||
|
||||
# select the 7th to last note for spectrum measurement
|
||||
|
||||
@ -423,33 +454,47 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbL
|
||||
|
||||
|
||||
# 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, dbLinRef, spectrumSmpIdx)
|
||||
rmsDbV, rms_srate, specV, specHopIdx, binHz = ra.audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, spectrumSmpIdx)
|
||||
|
||||
# specV[] is the spectrum of the note at spectrumSmpIdx
|
||||
|
||||
# plot the spectrum and the harmonic selection ranges
|
||||
plot_spectrum( axL[plot_idx], srate, binHz, specV, midiPitch, harmN )
|
||||
|
||||
axL[-1].set_xlabel("Hertz")
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"plot_spectral_ranges.png"),format="png")
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
|
||||
def td_plot( ax, inDir, midi_pitch, id, analysisArgsD ):
|
||||
|
||||
#
|
||||
r = rms_analysis_main( inDir, midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
|
||||
|
||||
# find min/max peak in the sequence
|
||||
min_pk_idx, max_pk_idx = find_min_max_peak_index( r.pkDbL, analysisArgsD['minAttkDb'], analysisArgsD['maxDbOffset'] )
|
||||
|
||||
# find ranges of the sequence which should be skipped because they are noisy or unreliable
|
||||
skipPkIdxL = find_skip_peaks( r.rmsDbV, r.pkIdxL, min_pk_idx, max_pk_idx )
|
||||
|
||||
# find peaks whose difference to surrounding peaks is greater than 'maxDeltaDb'.
|
||||
jmpPkIdxL = find_out_of_range_peaks( r.rmsDbV, r.pkIdxL, min_pk_idx, max_pk_idx, analysisArgsD['maxDeltaDb'] )
|
||||
|
||||
|
||||
secV = np.arange(0,len(r.rmsDbV)) / r.rms_srate
|
||||
|
||||
ax.plot( secV, r.rmsDbV )
|
||||
ax.plot( np.arange(0,len(r.tdRmsDbV)) / r.rms_srate, r.tdRmsDbV, color="black" )
|
||||
# plot the harmonic RMS signal
|
||||
ax.plot( secV, r.rmsDbV, color='blue', label="Harmonic" )
|
||||
|
||||
# plot the time-domain RMS signal
|
||||
ax.plot( np.arange(0,len(r.tdRmsDbV)) / r.rms_srate, r.tdRmsDbV, color="black", label="TD" )
|
||||
|
||||
|
||||
# print beg/end boundaries
|
||||
# print note beg/end/peak boundaries
|
||||
for i,(begMs, endMs) in enumerate(r.eventTimeL):
|
||||
|
||||
pkSec = r.pkIdxL[i] / r.rms_srate
|
||||
@ -472,37 +517,44 @@ def td_plot( ax, inDir, midi_pitch, id, analysisArgsD ):
|
||||
ax.plot( [pki / r.rms_srate], [ r.rmsDbV[pki] ], marker=6, color="blue")
|
||||
|
||||
|
||||
ax.legend();
|
||||
ax.set_ylabel("dB");
|
||||
|
||||
return r
|
||||
|
||||
|
||||
|
||||
def do_td_plot( inDir, analysisArgs ):
|
||||
def do_td_plot( inDir, analysisArgs, midi_pitch, takeId, printDir="" ):
|
||||
|
||||
fig,axL = plt.subplots(3,1)
|
||||
fig.set_size_inches(18.5, 10.5, forward=True)
|
||||
|
||||
id = int(inDir.split("/")[-1])
|
||||
midi_pitch = int(inDir.split("/")[-2])
|
||||
# parse the file name
|
||||
inDir = os.path.join(inDir,str(midi_pitch),str(takeId));
|
||||
|
||||
r = td_plot(axL[0],inDir,midi_pitch,id,analysisArgs)
|
||||
# plot the time domain signal
|
||||
r = td_plot(axL[0],inDir,midi_pitch,takeId,analysisArgs)
|
||||
|
||||
qualityV = np.array([ x.quality for x in r.statsL ]) * np.max(r.pkDbL)
|
||||
durMsV = np.array([ x.durMs for x in r.statsL ])
|
||||
avgV = np.array([ x.durAvgDb for x in r.statsL ])
|
||||
|
||||
#durMsV[ durMsV < 400 ] = 0
|
||||
#durMsV = durMsV * np.max(r.pkDbL)/np.max(durMsV)
|
||||
#durMsV = durMsV / 100.0
|
||||
|
||||
dV = np.diff(r.pkDbL) / r.pkDbL[1:]
|
||||
|
||||
axL[1].plot( r.pkUsL, r.pkDbL, marker='.',label="pkDb" )
|
||||
axL[1].plot( r.pkUsL, qualityV, marker='.',label="quality" )
|
||||
axL[1].plot( r.pkUsL, avgV, marker='.',label="avgDb" )
|
||||
#axL[2].plot( r.pkUsL, durMsV, marker='.' )
|
||||
axL[2].plot( r.pkUsL[1:], dV, marker='.',label='d')
|
||||
axL[2].set_ylim([-1,1])
|
||||
axL[1].plot( r.pkUsL, r.pkDbL, marker='.', label="harmonic" )
|
||||
#axL[1].plot( r.pkUsL, qualityV, marker='.', label="quality" )
|
||||
axL[1].plot( r.pkUsL, avgV, marker='.', label="harm-td avg" )
|
||||
axL[1].legend()
|
||||
axL[1].set_ylabel("dB");
|
||||
|
||||
#axL[2].plot( r.pkUsL, durMsV, marker='.' )
|
||||
axL[2].plot( r.pkUsL[1:], dV, marker='.',label='delta')
|
||||
axL[2].set_ylim([-1,1])
|
||||
axL[2].legend()
|
||||
axL[2].set_ylabel("dB");
|
||||
|
||||
axL[2].set_xlabel("Microseconds")
|
||||
|
||||
|
||||
sni = select_first_stable_note_by_dur( durMsV )
|
||||
@ -520,40 +572,62 @@ def do_td_plot( inDir, analysisArgs ):
|
||||
for i in range(1,len(r.pkUsL)):
|
||||
axL[2].text( r.pkUsL[i], dV[i-1], "%i" % (i))
|
||||
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"do_td_plot.png"),format="png")
|
||||
plt.show()
|
||||
|
||||
def do_td_multi_plot( inDir, analysisArgs ):
|
||||
def do_td_multi_plot( inDir, analysisArgs, pitchTakeL, printPlotFl=False ):
|
||||
|
||||
midi_pitch = int(inDir.split("/")[-1])
|
||||
#midi_pitch = int(inDir.split("/")[-1])
|
||||
|
||||
dirL = os.listdir(inDir)
|
||||
|
||||
fig,axL = plt.subplots(len(dirL),1)
|
||||
fig,axL = plt.subplots(len(pitchTakeL),1)
|
||||
|
||||
|
||||
for id,(idir,ax) in enumerate(zip(dirL,axL)):
|
||||
for id,((pitch,takeId),ax) in enumerate(zip(pitchTakeL,axL)):
|
||||
|
||||
td_plot(ax, os.path.join(inDir,str(id)), midi_pitch, id, analysisArgs )
|
||||
td_plot(ax, os.path.join(inDir,str(pitch),str(takeId)), pitch, takeId, analysisArgs )
|
||||
|
||||
ax.set_xlabel("Seconds")
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"multi_plot.png"),format="png")
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
inDir = sys.argv[1]
|
||||
cfgFn = sys.argv[2]
|
||||
take_id = None if len(sys.argv)<4 else sys.argv[3]
|
||||
printDir = os.path.expanduser("~/src/picadae_ac_3/doc")
|
||||
cfgFn = sys.argv[1]
|
||||
inDir = sys.argv[2]
|
||||
mode = sys.argv[3]
|
||||
|
||||
cfg = parse_yaml_cfg( cfgFn )
|
||||
|
||||
if take_id is not None:
|
||||
inDir = os.path.join(inDir,take_id)
|
||||
do_td_plot(inDir,cfg.analysisArgs)
|
||||
if mode == "td_plot":
|
||||
|
||||
# python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_plot 60 10
|
||||
pitch = int(sys.argv[4])
|
||||
take_id = int(sys.argv[5])
|
||||
do_td_plot(inDir,cfg.analysisArgs, pitch, take_id, printDir )
|
||||
|
||||
elif mode == "td_multi_plot" or mode == 'plot_spectral_ranges':
|
||||
|
||||
pitchTakeIdL = []
|
||||
for i in range(4,len(sys.argv),2):
|
||||
pitchTakeIdL.append( (int(sys.argv[i]), int(sys.argv[i+1])) )
|
||||
|
||||
if mode == "td_multi_plot":
|
||||
# python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 36 2 48 3 60 4
|
||||
do_td_multi_plot(inDir, cfg.analysisArgs, pitchTakeIdL, printDir )
|
||||
else:
|
||||
#do_td_multi_plot(inDir,cfg.analysisArgs)
|
||||
# python plot_seq.py p_ac.yml ~/temp/p_ac_3_od plot_spectral_ranges 36 2 48 3 60 4
|
||||
plot_spectral_ranges( inDir, pitchTakeIdL, printDir=printDir )
|
||||
|
||||
#plot_spectral_ranges( inDir, [ 24, 36, 48, 60, 72, 84, 96, 104] )
|
||||
elif mode == "resample_pulse_times":
|
||||
|
||||
plot_resample_pulse_times( inDir, cfg.analysisArgs )
|
||||
# python plot_seq.py p_ac.yml ~/temp/p_ac_3_od resample_pulse_times 60
|
||||
pitch = int(sys.argv[4])
|
||||
plot_resample_pulse_times( inDir, cfg.analysisArgs, pitch, printDir )
|
||||
|
||||
else:
|
||||
print("Unknown plot mode:%s" % (mode))
|
||||
|
@ -14,6 +14,7 @@ def fit_to_reference( pkL, refTakeId ):
|
||||
dur_outL = []
|
||||
tid_outL = []
|
||||
|
||||
|
||||
dbL,usL,durMsL,takeIdL = tuple(zip(*pkL))
|
||||
|
||||
us_refL,db_refL,dur_refL = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==refTakeId])
|
||||
@ -27,6 +28,8 @@ def fit_to_reference( pkL, refTakeId ):
|
||||
db_outL += db0L
|
||||
else:
|
||||
db1V = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
|
||||
|
||||
if db1V is not None:
|
||||
db_outL += db1V.tolist()
|
||||
|
||||
us_outL += us0L
|
||||
@ -44,11 +47,17 @@ def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
|
||||
|
||||
pkL = []
|
||||
|
||||
refTakeId = None
|
||||
usRefL = None
|
||||
dbRefL = None
|
||||
|
||||
# for each take in this directory
|
||||
for take_number in range(len(takeDirL)):
|
||||
for take_folder in takeDirL:
|
||||
|
||||
take_number = int(take_folder)
|
||||
|
||||
if refTakeId is None:
|
||||
refTakeId = take_number
|
||||
|
||||
# analyze this takes audio and locate the note peaks
|
||||
r = rms_analysis.rms_analysis_main( os.path.join(inDir,str(take_number)), midi_pitch, **analysisArgsD )
|
||||
@ -57,7 +66,18 @@ def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
|
||||
for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
|
||||
pkL.append( (db,us,stats.durMs,take_number) )
|
||||
|
||||
pkL = fit_to_reference( pkL, 0 )
|
||||
|
||||
pkUsL = []
|
||||
pkDbL = []
|
||||
durMsL = []
|
||||
takeIdL = []
|
||||
holdDutyPctL = []
|
||||
|
||||
if refTakeId is None:
|
||||
print("No valid data files at %s pitch:%i" % (inDir,midi_pitch))
|
||||
else:
|
||||
|
||||
pkL = fit_to_reference( pkL, refTakeId )
|
||||
|
||||
# sort the peaks on increasing attack pulse microseconds
|
||||
pkL = sorted( pkL, key= lambda x: x[1] )
|
||||
@ -220,6 +240,7 @@ def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
|
||||
def plot_us_db_curves( ax, inDir, keyMapD, midi_pitch, analysisArgsD, plotResamplePointsFl=False, plotTakesFl=True, usMax=None ):
|
||||
|
||||
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
|
||||
|
||||
reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
|
||||
|
||||
# plot first audible and non-skip position
|
||||
@ -303,7 +324,7 @@ def plot_us_db_curves( ax, inDir, keyMapD, midi_pitch, analysisArgsD, plotResamp
|
||||
|
||||
ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
|
||||
|
||||
def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None ):
|
||||
def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None, printDir="" ):
|
||||
|
||||
analysisArgsD = cfg.analysisArgs
|
||||
keyMapD = { d['midi']:d for d in cfg.key_mapL }
|
||||
@ -311,6 +332,7 @@ def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None ):
|
||||
fig,axL = plt.subplots(axN,1,sharex=True)
|
||||
if axN == 1:
|
||||
axL = [axL]
|
||||
|
||||
fig.set_size_inches(18.5, 10.5*axN)
|
||||
|
||||
for ax,midi_pitch in zip(axL,pitchL):
|
||||
@ -319,6 +341,9 @@ def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None ):
|
||||
if plotTakesFl:
|
||||
plt.legend()
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"us_db.png"),format="png")
|
||||
|
||||
plt.show()
|
||||
|
||||
def plot_all_noise_curves( inDir, cfg, pitchL=None ):
|
||||
@ -332,16 +357,16 @@ def plot_all_noise_curves( inDir, cfg, pitchL=None ):
|
||||
|
||||
for midi_pitch in pitchL:
|
||||
|
||||
print(midi_pitch)
|
||||
|
||||
usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
|
||||
|
||||
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
|
||||
|
||||
minDurMs = cfg.analysisArgs['resampleMinDurMs']
|
||||
minDb = cfg.analysisArgs['resampleMinDb'],
|
||||
minDb = cfg.analysisArgs['resampleMinDb']
|
||||
noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
|
||||
|
||||
|
||||
|
||||
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, scoreV.tolist(), takeIdL, minDurMs, minDb, noiseLimitPct )
|
||||
|
||||
|
||||
@ -364,18 +389,22 @@ def plot_all_noise_curves( inDir, cfg, pitchL=None ):
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
|
||||
def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
|
||||
|
||||
pitchFolderL = os.listdir(inDir)
|
||||
|
||||
print(pitchL)
|
||||
|
||||
if pitchL is None:
|
||||
pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
|
||||
|
||||
print(pitchL)
|
||||
|
||||
okL = []
|
||||
outPitchL = []
|
||||
minDbL = []
|
||||
maxDbL = []
|
||||
|
||||
for midi_pitch in pitchL:
|
||||
|
||||
print(midi_pitch)
|
||||
@ -384,12 +413,9 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
|
||||
|
||||
okL.append(False)
|
||||
|
||||
takeId = len(set(takeIdL))-1
|
||||
|
||||
db_maxL = sorted(dbL)
|
||||
maxDbL.append( np.mean(db_maxL[-5:]) )
|
||||
|
||||
|
||||
usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId ])
|
||||
|
||||
if len(set(takeIdL)) == 3:
|
||||
@ -415,9 +441,13 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
|
||||
ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
|
||||
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"min_max_db_2.png"),format="png")
|
||||
|
||||
|
||||
plt.show()
|
||||
|
||||
def plot_min_db_manual( inDir, cfg ):
|
||||
def plot_min_db_manual( inDir, cfg, printDir=None ):
|
||||
|
||||
pitchL = list(cfg.manualMinD.keys())
|
||||
|
||||
@ -466,6 +496,8 @@ def plot_min_db_manual( inDir, cfg ):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Form the complete set of min/max db levels for each pitch by interpolating the
|
||||
# db values between the manually selected anchor points.
|
||||
interpMinDbL = np.interp( pitchL, cfg.manualAnchorPitchMinDbL, anchorMinDbL )
|
||||
@ -493,13 +525,12 @@ def plot_min_db_manual( inDir, cfg ):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"manual_db.png"),format="png")
|
||||
|
||||
plt.show()
|
||||
|
||||
def plot_min_max_db( inDir, cfg, pitchL=None ):
|
||||
def plot_min_max_db( inDir, cfg, pitchL=None, printDir=None ):
|
||||
|
||||
pitchFolderL = os.listdir(inDir)
|
||||
|
||||
@ -518,7 +549,7 @@ def plot_min_max_db( inDir, cfg, pitchL=None ):
|
||||
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
|
||||
|
||||
minDurMs = cfg.analysisArgs['resampleMinDurMs']
|
||||
minDb = cfg.analysisArgs['resampleMinDb'],
|
||||
minDb = cfg.analysisArgs['resampleMinDb']
|
||||
noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
|
||||
|
||||
skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )
|
||||
@ -548,6 +579,9 @@ def plot_min_max_db( inDir, cfg, pitchL=None ):
|
||||
|
||||
ax.text( pitch, db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']))
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"min_max_db.png"),format="png")
|
||||
|
||||
|
||||
plt.show()
|
||||
|
||||
@ -575,7 +609,7 @@ def estimate_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=0
|
||||
scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
|
||||
|
||||
minDurMs = cfg.analysisArgs['resampleMinDurMs']
|
||||
minDb = cfg.analysisArgs['resampleMinDb'],
|
||||
minDb = cfg.analysisArgs['resampleMinDb']
|
||||
noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
|
||||
|
||||
# get the set of samples that are not valid (too short, too quiet, too noisy)
|
||||
@ -633,7 +667,7 @@ def estimate_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=0
|
||||
|
||||
return mapD, list(dbS)
|
||||
|
||||
def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0, pitchL=None ):
|
||||
def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0, pitchL=None, printDir=None ):
|
||||
|
||||
fig,ax = plt.subplots()
|
||||
|
||||
@ -644,6 +678,8 @@ def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0,
|
||||
|
||||
u_dL = [ (d['us_avg'],d['us_cls'],d['db_avg'],d['us_std'],d['us_min'],d['us_max'],d['db_std']) for loDb, d in dbD.items() if d['us_avg'] != 0 ]
|
||||
|
||||
if u_dL:
|
||||
|
||||
# get the us/db lists for this pitch
|
||||
usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
|
||||
|
||||
@ -660,6 +696,10 @@ def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0,
|
||||
|
||||
|
||||
plt.legend()
|
||||
|
||||
if printDir:
|
||||
plt.savefig(os.path.join(printDir,"us_db_map.png"),format="png")
|
||||
|
||||
plt.show()
|
||||
|
||||
def report_take_ids( inDir ):
|
||||
@ -760,9 +800,11 @@ def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
inDir = sys.argv[1]
|
||||
cfgFn = sys.argv[2]
|
||||
printDir =os.path.expanduser( "~/src/picadae_ac_3/doc")
|
||||
cfgFn = sys.argv[1]
|
||||
inDir = sys.argv[2]
|
||||
mode = sys.argv[3]
|
||||
|
||||
if len(sys.argv) <= 4:
|
||||
pitchL = None
|
||||
else:
|
||||
@ -771,21 +813,23 @@ if __name__ == "__main__":
|
||||
cfg = parse_yaml_cfg( cfgFn )
|
||||
|
||||
if mode == 'us_db':
|
||||
plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True,usMax=None )
|
||||
plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True,usMax=None, printDir=printDir )
|
||||
elif mode == 'noise':
|
||||
plot_all_noise_curves( inDir, cfg, pitchL )
|
||||
elif mode == 'min_max':
|
||||
plot_min_max_db( inDir, cfg, pitchL )
|
||||
plot_min_max_db( inDir, cfg, pitchL, printDir=printDir )
|
||||
elif mode == 'min_max_2':
|
||||
plot_min_max_2_db( inDir, cfg, pitchL )
|
||||
takeId = pitchL[-1]
|
||||
del pitchL[-1]
|
||||
plot_min_max_2_db( inDir, cfg, pitchL, takeId=takeId, printDir=printDir )
|
||||
elif mode == 'us_db_map':
|
||||
plot_us_to_db_map( inDir, cfg, pitchL=pitchL )
|
||||
plot_us_to_db_map( inDir, cfg, pitchL=pitchL, printDir=printDir )
|
||||
elif mode == 'audacity':
|
||||
rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )
|
||||
elif mode == 'rpt_take_ids':
|
||||
report_take_ids( inDir )
|
||||
elif mode == 'manual_db':
|
||||
plot_min_db_manual( inDir, cfg )
|
||||
plot_min_db_manual( inDir, cfg, printDir=printDir )
|
||||
elif mode == 'gen_vel_map':
|
||||
gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" )
|
||||
elif mode == 'cache_us_db':
|
||||
|
@ -201,19 +201,22 @@ def select_first_stable_note_by_delta_db( pkDbL, pkUsL=None, maxPulseUs=0.1 ):
|
||||
return None
|
||||
|
||||
def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
|
||||
|
||||
""" Collect some statistics and markers for each note in the sequence. """
|
||||
statsL = []
|
||||
|
||||
srate = r.rms_srate
|
||||
|
||||
qmax = 0
|
||||
|
||||
|
||||
|
||||
for i,(begSmpMs, endSmpMs) in enumerate(r.eventTimeL):
|
||||
|
||||
begSmpIdx = int(round(srate * begSmpMs / 1000.0))
|
||||
endSmpIdx = int(round(srate * (endSmpMs + extraDurSearchMs) / 1000.0))
|
||||
pkSmpIdx = r.pkIdxL[i]
|
||||
|
||||
|
||||
durMs = measure_duration_ms( r.rmsDbV, srate, pkSmpIdx, endSmpIdx, decay_pct )
|
||||
|
||||
bi = pkSmpIdx
|
||||
@ -232,7 +235,15 @@ def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
|
||||
hmRmsDb_u = 0.0 if ei >= len(r.rmsDbV) else np.mean(r.rmsDbV[bi:ei])
|
||||
durAvgDb = (hmRmsDb_u + tdRmsDb_u)/2.0
|
||||
|
||||
statsL.append( types.SimpleNamespace(**{'begSmpSec':begSmpIdx/srate,'endSmpSec':endSmpIdx/srate,'pkSmpSec':pkSmpIdx/srate,'durMs':durMs, 'pkDb':r.pkDbL[i], 'pulse_us':r.pkUsL[i], 'quality':qualityCoeff, 'durAvgDb':durAvgDb }))
|
||||
statsL.append( types.SimpleNamespace(**{
|
||||
'begSmpSec':begSmpIdx/srate,
|
||||
'endSmpSec':endSmpIdx/srate,
|
||||
'pkSmpSec':pkSmpIdx/srate,
|
||||
'durMs':durMs,
|
||||
'pkDb':r.pkDbL[i],
|
||||
'pulse_us':r.pkUsL[i],
|
||||
'quality':qualityCoeff,
|
||||
'durAvgDb':durAvgDb }))
|
||||
|
||||
for i,r in enumerate(statsL):
|
||||
statsL[i].quality = 0 if qmax <= 0 else statsL[i].quality / qmax
|
||||
@ -243,15 +254,19 @@ def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
|
||||
|
||||
|
||||
|
||||
def locate_peak_indexes( xV, xV_srate, eventMsL ):
|
||||
def locate_peak_indexes( xV, xV_srate, eventMsL, audioFn ):
|
||||
|
||||
pkIdxL = []
|
||||
for begMs, endMs in eventMsL:
|
||||
for i, (begMs, endMs) in enumerate(eventMsL):
|
||||
|
||||
begSmpIdx = int(begMs * xV_srate / 1000.0)
|
||||
endSmpIdx = int(endMs * xV_srate / 1000.0)
|
||||
|
||||
if endSmpIdx != begSmpIdx:
|
||||
pkIdxL.append( begSmpIdx + np.argmax( xV[begSmpIdx:endSmpIdx] ) )
|
||||
else:
|
||||
print("Missing peak %i : begween beg:%i ms end:%i ms : %s" % (i, begMs, endMs, audioFn ))
|
||||
pkIdxL.append( None )
|
||||
|
||||
return pkIdxL
|
||||
|
||||
@ -304,6 +319,11 @@ def rms_analyze_one_rt_note_wrap( audioDev, annBegMs, annEndMs, midi_pitch, note
|
||||
|
||||
sigV = buf_result.value
|
||||
|
||||
if len(sigV.shape) > 1:
|
||||
sigV = sigV[:,0].squeeze()
|
||||
|
||||
|
||||
|
||||
# get the annotated begin and end of the note as sample indexes into sigV
|
||||
bi = int(round(annBegMs * audioDev.srate / 1000))
|
||||
ei = int(round(annEndMs * audioDev.srate / 1000))
|
||||
@ -399,47 +419,72 @@ def calibrate_recording_analysis( inDir ):
|
||||
|
||||
def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
|
||||
|
||||
# form the audio and meta data file names
|
||||
seqFn = os.path.join( inDir, "seq.json")
|
||||
audioFn = os.path.join( inDir, "audio.wav")
|
||||
|
||||
|
||||
# read the meta data file
|
||||
with open( seqFn, "rb") as f:
|
||||
r = json.load(f)
|
||||
|
||||
|
||||
# rad the auido file
|
||||
srate, signalM = wavfile.read(audioFn)
|
||||
|
||||
# convert the audio signal vector to contain only the first (left) channel
|
||||
if len(signalM.shape)>1:
|
||||
signalM = signalM[:,0].squeeze()
|
||||
|
||||
# convert the auido file to floats in range [-1.0 to 1.0]
|
||||
sigV = signalM / float(0x7fff)
|
||||
|
||||
# calc. the RMS signal
|
||||
tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
|
||||
|
||||
tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate, r['eventTimeL'])
|
||||
# locate the peaks in the RMS signal
|
||||
tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate, r['eventTimeL'], audioFn )
|
||||
|
||||
# sum the first harmN harmonics to form a envelope of the audio signal (this is an alternative to the RMS signal)
|
||||
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'] )
|
||||
# locate the peaks in the harmonic sum signal
|
||||
pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'], audioFn )
|
||||
|
||||
# form the 'holdDutyPctlL' hold duty cycle transition point list
|
||||
holdDutyPctL = None
|
||||
if 'holdDutyPct' in r:
|
||||
holdDutyPctL = [ (0, r['holdDutyPct']) ]
|
||||
else:
|
||||
holdDutyPctL = r['holdDutyPctL']
|
||||
|
||||
eventN = len(r['eventTimeL'])
|
||||
assert( len(tdPkIdxL) == eventN and len(pkIdxL) == eventN )
|
||||
|
||||
# filter out all missing events that have no peak
|
||||
flL = [ (tdPkIdxL[i] is not None) and (pkIdxL[i] is not None) for i in range(eventN) ]
|
||||
eventTimeL = [ r['eventTimeL'][i] for i in range(eventN) if flL[i] ]
|
||||
tdPkDbL = [ tdRmsDbV[tdPkIdxL[i]] for i in range(eventN) if flL[i] ]
|
||||
pkDbL = [ rmsDbV[ pkIdxL[i]] for i in range(eventN) if flL[i] ]
|
||||
#pkUsL = [ r['pulseUsL'][pkIdxL[i]] for i in range(eventN) if flL[i] ]
|
||||
tdPkIdxL = [ i for i in tdPkIdxL if i is not None ]
|
||||
pkIdxL = [ i for i in pkIdxL if i is not None ]
|
||||
|
||||
# form the result record
|
||||
r = types.SimpleNamespace(**{
|
||||
"audio_srate":srate,
|
||||
"eventTimeMsL":r['eventTimeL'],
|
||||
"eventTimeMsL": eventTimeL, #r['eventTimeL'],
|
||||
"tdRmsDbV": tdRmsDbV,
|
||||
"tdPkIdxL": tdPkIdxL,
|
||||
"tdPkDbL": [ tdRmsDbV[i] for i in tdPkIdxL ],
|
||||
"tdPkDbL": tdPkDbL, # [ tdRmsDbV[i] for i in tdPkIdxL ],
|
||||
"binHz": binHz,
|
||||
"rmsDbV": rmsDbV,
|
||||
"rms_srate":rms_srate,
|
||||
"pkIdxL":pkIdxL, # pkIdxL[ len(pulsUsL) ] - indexes into rmsDbV[] of peaks
|
||||
"eventTimeL":r['eventTimeL'],
|
||||
"eventTimeL": eventTimeL, #r['eventTimeL'],
|
||||
"holdDutyPctL":holdDutyPctL,
|
||||
'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ],
|
||||
'pkUsL':r['pulseUsL'] })
|
||||
'pkDbL': pkDbL, # [ rmsDbV[ i ] for i in pkIdxL ],
|
||||
'pkUsL': r['pulseUsL'] }) # r['pulseUsL']
|
||||
|
||||
#
|
||||
statsL = note_stats(r,durDecayPct)
|
||||
|
||||
setattr(r,"statsL", statsL )
|
||||
|