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 @@ - - - - - - - - - 2020-11-24T13:43:07.097753 - image/svg+xml - - - Matplotlib v3.3.0, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 )