diff --git a/README.md b/README.md
index 901da50..07795b5 100644
--- a/README.md
+++ b/README.md
@@ -34,12 +34,51 @@ python calibrate_plot.py ~/temp/p_ac_3_oa/60/2 p_ac.yml 60
-![Plot Seq 1](doc/plot_seq_0.png)
-
-`python plot_seq.py ~/temp/p_ac_3_od/60 p_ac.yml 12`
-
+![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 ],
+
+````
\ No newline at end of file
diff --git a/do_td_plot.png b/do_td_plot.png
new file mode 100644
index 0000000..778c944
Binary files /dev/null and b/do_td_plot.png differ
diff --git a/doc/do_td_plot.png b/doc/do_td_plot.png
new file mode 100644
index 0000000..11901c0
Binary files /dev/null and b/doc/do_td_plot.png differ
diff --git a/doc/manual_db.png b/doc/manual_db.png
new file mode 100644
index 0000000..389cc51
Binary files /dev/null and b/doc/manual_db.png differ
diff --git a/doc/min_max_db_2.png b/doc/min_max_db_2.png
new file mode 100644
index 0000000..32f8d5f
Binary files /dev/null and b/doc/min_max_db_2.png differ
diff --git a/doc/multi_plot.png b/doc/multi_plot.png
index 0e010f2..bba7622 100644
Binary files a/doc/multi_plot.png and b/doc/multi_plot.png differ
diff --git a/doc/multi_plot.svg b/doc/multi_plot.svg
deleted file mode 100644
index 13993cc..0000000
--- a/doc/multi_plot.svg
+++ /dev/null
@@ -1,6060 +0,0 @@
-
-
-
-
diff --git a/doc/plot_seq_0.png b/doc/plot_seq_0.png
deleted file mode 100644
index 81a1623..0000000
Binary files a/doc/plot_seq_0.png and /dev/null differ
diff --git a/doc/plot_spectral_ranges.png b/doc/plot_spectral_ranges.png
new file mode 100644
index 0000000..a07096f
Binary files /dev/null and b/doc/plot_spectral_ranges.png differ
diff --git a/doc/us_db_map.png b/doc/us_db_map.png
new file mode 100644
index 0000000..0daf23c
Binary files /dev/null and b/doc/us_db_map.png differ
diff --git a/p_ac.yml b/p_ac.yml
index 784e0af..673ce8e 100644
--- a/p_ac.yml
+++ b/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,15 +58,44 @@
full_pulse8L: [ 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
+ # full_pulse9L 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_pulseL: [11000, 11075, 11150, 11225, 11300, 11375, 11450, 11525, 11600,11675, 11750, 11825, 11900, 11975, 12050, 12125, 12200, 12275,12350, 12425, 12500, 12575, 12650, 12725, 12800, 12875, 12950, 13025, 13100, 13175, 13250, 13325, 13400, 13475, 13550, 13625, 13700, 13775, 13850, 13925, 14000, 14075, 14150, 14225, 14300, 14375, 14450, 14525, 14600, 14675, 14750, 14825, 14900, 14975],
-
full_pulse10L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000, 15250, 15375, 15500, 15750, 16000, 16250, 16500, 16750, 17000, 17250, 17500, 17750, 18000, 18250, 18500, 18750, 19000, 19500, 20000, 20500, 21000, 21500, 22000, 22500, 23000, 23500, 24000, 24500, 25000, 25500, 26000, 26500, 27000, 27500, 28000, 28500, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000 ],
full_pulse11L: [ 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000 ],
full_pulse12L: [ 8750, 8800, 8850, 8900, 8950, 9000, 9050, 9100, 9150, 9200, 9250, 9300, 9350, 9400, 9450,9500, 9550, 9600, 9650, 9700, 9750, 9800, 9850, 9900, 9950, 10000, 10050, 10100, 10150, 10200, 10250, 10300, 10350, 10400, 10450, 10500, 10550, 10600, 10650, 10700, 10750, 10800, 10850, 10900, 10950, 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875, 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875, 15000 ],
+
+ # 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
-
- minAttkDb: 7.0, # threshold of silence level
+ resampleMinDurMs: 150, # notes's whose duration is less than this will be skipped
+
+ 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]],
diff --git a/plot_seq.py b/plot_seq.py
index f09ba90..eebb3a2 100644
--- a/plot_seq.py
+++ b/plot_seq.py
@@ -1,14 +1,22 @@
##| Copyright: (C) 2019-2020 Kevin Larke
##| 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:
@@ -46,10 +57,18 @@ 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'] )
-
+
# store the peaks in pkL[ (db,us) ]
for db,us in zip(r.pkDbL,r.pkUsL):
pkL.append( (db,us) )
@@ -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,33 +410,40 @@ 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:
r = json.load(f)
-
+
# 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" )
+ secV = np.arange(0,len(r.rmsDbV)) / r.rms_srate
+
+ # 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,38 +517,45 @@ 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 )
if sni is not None:
@@ -519,41 +571,63 @@ 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 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 )
- if take_id is not None:
- inDir = os.path.join(inDir,take_id)
- do_td_plot(inDir,cfg.analysisArgs)
- else:
- #do_td_multi_plot(inDir,cfg.analysisArgs)
+ elif mode == "td_multi_plot" or mode == 'plot_spectral_ranges':
- #plot_spectral_ranges( inDir, [ 24, 36, 48, 60, 72, 84, 96, 104] )
+ pitchTakeIdL = []
+ for i in range(4,len(sys.argv),2):
+ pitchTakeIdL.append( (int(sys.argv[i]), int(sys.argv[i+1])) )
- plot_resample_pulse_times( inDir, cfg.analysisArgs )
+ 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:
+ # 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 )
+
+ elif mode == "resample_pulse_times":
+ # 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))
diff --git a/plot_seq_1.py b/plot_seq_1.py
index c4572a3..6ac3080 100644
--- a/plot_seq_1.py
+++ b/plot_seq_1.py
@@ -14,8 +14,9 @@ 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])
@@ -26,8 +27,10 @@ def fit_to_reference( pkL, refTakeId ):
if takeId == refTakeId:
db_outL += db0L
else:
- db1V = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
- db_outL += db1V.tolist()
+ db1V = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
+
+ if db1V is not None:
+ db_outL += db1V.tolist()
us_outL += us0L
dur_outL+= dur0L
@@ -37,18 +40,24 @@ def fit_to_reference( pkL, refTakeId ):
def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
-
+
inDir = os.path.join(inDir,"%i" % (midi_pitch))
takeDirL = os.listdir(inDir)
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,16 +66,27 @@ 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 )
-
- # sort the peaks on increasing attack pulse microseconds
- pkL = sorted( pkL, key= lambda x: x[1] )
- # merge sample points that separated by less than 'minSampleDistUs' milliseconds
- #pkL = merge_close_sample_points( pkL, analysisArgsD['minSampleDistUs'] )
+ pkUsL = []
+ pkDbL = []
+ durMsL = []
+ takeIdL = []
+ holdDutyPctL = []
- # split pkL
- pkDbL,pkUsL,durMsL,takeIdL = tuple(zip(*pkL))
+ 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] )
+
+ # merge sample points that separated by less than 'minSampleDistUs' milliseconds
+ #pkL = merge_close_sample_points( pkL, analysisArgsD['minSampleDistUs'] )
+
+ # split pkL
+ pkDbL,pkUsL,durMsL,takeIdL = tuple(zip(*pkL))
return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
@@ -208,7 +228,7 @@ def get_resample_points( usL, dbL, durMsL, takeIdL, minDurMs, minDb, noiseLimitP
return reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx
def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
-
+
usL, dbL, durMsL,_,_ = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
reUsL,_,_,_,_,_,_ = get_resample_points( usL, dbL, durMsL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
@@ -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,14 +324,15 @@ 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 }
- axN = len(pitchL)
- fig,axL = plt.subplots(axN,1,sharex=True)
+ keyMapD = { d['midi']:d for d in cfg.key_mapL }
+ axN = len(pitchL)
+ 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):
@@ -318,6 +340,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()
@@ -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 ]
-
- okL = []
+ print(pitchL)
+
+ okL = []
outPitchL = []
- minDbL = []
- maxDbL = []
+ minDbL = []
+ maxDbL = []
+
for midi_pitch in pitchL:
print(midi_pitch)
@@ -384,11 +413,8 @@ 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 ])
@@ -414,10 +440,14 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
-
+
+ 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())
@@ -463,8 +493,10 @@ def plot_min_db_manual( inDir, cfg ):
if midi_pitch in cfg.manualAnchorPitchMaxDbL:
anchorMaxDbL.append( max_db )
-
+
+
+
# Form the complete set of min/max db levels for each pitch by interpolating the
# db values between the manually selected anchor points.
@@ -491,15 +523,14 @@ def plot_min_db_manual( inDir, cfg ):
with open("minInterpDb.json",'w') as f:
json.dump( { "pitchL":pitchL, "minDbL":list(interpMinDbL), "maxDbL":list(interpMaxDbL) }, f )
-
-
-
+
-
+ 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,22 +678,28 @@ 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 ]
- # get the us/db lists for this pitch
- usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
+ if u_dL:
+
+ # get the us/db lists for this pitch
+ usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
- # plot central curve and std dev's
- p = ax.plot(usL,dbL, marker='.', label=str(pitch))
- ax.plot(uscL,dbL, marker='x', label=str(pitch), color=p[0].get_color(), linestyle='None')
- ax.plot(usL,np.array(dbL)+dbsL, color=p[0].get_color(), alpha=0.3)
- ax.plot(usL,np.array(dbL)-dbsL, color=p[0].get_color(), alpha=0.3)
+ # plot central curve and std dev's
+ p = ax.plot(usL,dbL, marker='.', label=str(pitch))
+ ax.plot(uscL,dbL, marker='x', label=str(pitch), color=p[0].get_color(), linestyle='None')
+ ax.plot(usL,np.array(dbL)+dbsL, color=p[0].get_color(), alpha=0.3)
+ ax.plot(usL,np.array(dbL)-dbsL, color=p[0].get_color(), alpha=0.3)
- # plot us error bars
- for db,us,uss,us_min,us_max in zip(dbL,usL,ussL,usnL,usxL):
- ax.plot([us_min,us_max],[db,db], color=p[0].get_color(), alpha=0.3 )
- ax.plot([us-uss,us+uss],[db,db], color=p[0].get_color(), alpha=0.3, marker='.', linestyle='None' )
+ # plot us error bars
+ for db,us,uss,us_min,us_max in zip(dbL,usL,ussL,usnL,usxL):
+ ax.plot([us_min,us_max],[db,db], color=p[0].get_color(), alpha=0.3 )
+ ax.plot([us-uss,us+uss],[db,db], color=p[0].get_color(), alpha=0.3, marker='.', linestyle='None' )
plt.legend()
+
+ 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]
- mode = sys.argv[3]
+ 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':
diff --git a/rms_analysis.py b/rms_analysis.py
index 512123b..ad27011 100644
--- a/rms_analysis.py
+++ b/rms_analysis.py
@@ -201,12 +201,14 @@ 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):
@@ -214,6 +216,7 @@ def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
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)
- pkIdxL.append( begSmpIdx + np.argmax( xV[begSmpIdx:endSmpIdx] ) )
+ 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,
+ "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 )