9 Commit

Autore SHA1 Messaggio Data
  kevin ee3c41fe70 README.md,doc/*.png : Updates to verify plotting and add example plots to README.md 3 anni fa
  kevin c017cafe51 AudioDevice.py : Change default channel count from 1 (mono) to 2 (stereo). 3 anni fa
  kevin a163756f04 README.md : Experiments with adding images. 3 anni fa
  kevin 9ecb4d41b4 doc/multi_plot.svg : Update 3 anni fa
  kevin 349a6be063 Merge branch 'master' of gitea.larke.org:kevin/picadae_ac_3 3 anni fa
  kevin 38783413b6 doc/multi_port.svg : Update 3 anni fa
  kevin 1254547ce0 doc/multi_port.svg : Update 3 anni fa
  kevin 9b998596b7 Added images do doc/ 3 anni fa
  kevin 49ef663176 README.md : Added first plot image. 3 anni fa
13 ha cambiato i file con 529 aggiunte e 158 eliminazioni
  1. 3
    3
      AudioDevice.py
  2. 82
    0
      README.md
  3. BIN
      do_td_plot.png
  4. BIN
      doc/do_td_plot.png
  5. BIN
      doc/manual_db.png
  6. BIN
      doc/min_max_db_2.png
  7. BIN
      doc/multi_plot.png
  8. BIN
      doc/plot_spectral_ranges.png
  9. BIN
      doc/us_db_map.png
  10. 142
    16
      p_ac.yml
  11. 137
    63
      plot_seq.py
  12. 103
    59
      plot_seq_1.py
  13. 62
    17
      rms_analysis.py

+ 3
- 3
AudioDevice.py Vedi File

18
         self.bufL          = []
18
         self.bufL          = []
19
         self.bufIdx        = -1
19
         self.bufIdx        = -1
20
         self.srate         = 0
20
         self.srate         = 0
21
-        self.ch_cnt        = 1
21
+        self.ch_cnt        = 2
22
         
22
         
23
 
23
 
24
     def setup( self, **kwargs  ):
24
     def setup( self, **kwargs  ):
63
             }
63
             }
64
 
64
 
65
         return d
65
         return d
66
-    
66
+
67
     def get_port_list( self, inFl ):
67
     def get_port_list( self, inFl ):
68
 
68
 
69
         devLabelL = str(sd.query_devices()).split("\n")
69
         devLabelL = str(sd.query_devices()).split("\n")
70
-
70
+        
71
         portL = []
71
         portL = []
72
         for i,dev in enumerate(sd.query_devices()):
72
         for i,dev in enumerate(sd.query_devices()):
73
             isInputFl =  dev['max_input_channels']  > 0
73
             isInputFl =  dev['max_input_channels']  > 0

+ 82
- 0
README.md Vedi File

1
 * Picadae Calibration Programs
1
 * Picadae Calibration Programs
2
 
2
 
3
+sudo dnf install python3-devel jack-audio-connection-kit-devel
4
+
5
+
6
+prerequisites:
7
+pyyaml
8
+sounddevice
9
+soundfile
10
+pthon-rtmidi
11
+
12
+export PYTHONPATH=${HOME}/src/picadae/control/app
13
+python p_ac.py -c p_ac.yml
14
+
15
+c 60 60 # caputre note 60 using the full_pulseL[] from the p_ac.yml
16
+
17
+
18
+
19
+
20
+
21
+
22
+# plot third take of note 60 
23
+python plot_seq.py ~/temp/p_ac_3_oa/60 p_ac.yml 3
24
+
25
+# record a calibration take: CalibrateKeys.start
26
+uses cfg.full_pulseL[] for the pulse sequence
27
+use cfg.calibrateArgs.holdDutyPctD{} for hold PWM duty cycle
28
+
29
+# obsolete plotting function in calibrate_plot.py
30
+python calibrate_plot.py ~/temp/p_ac_3_oa/60/2 p_ac.yml 60
31
+
32
+
33
+
34
+
35
+
36
+
37
+![Plot Seq 1](doc/do_td_plot.png)
38
+`python plot_seq.py ~/temp/p_ac_3_od p_ac.yml 60 10`
39
+`do_td_plot(inDir,cfg.analysisArgs, pitch, take_id )`
40
+
41
+![Multi Plot 1](doc/multi_plot.png)
42
+`python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 60 3 60 4 60 5`
43
+plot_seq.py `do_td_multi_plot(inDir,cfg.analysisArgs,[(36,4), (48,2)] )`
44
+
45
+![Spectral Ranges](doc/plot_spectral_ranges.png)
46
+`python plot_seq.py p_ac.yml ~/temp/p_ac_3_od plot_spectral_ranges 60 3 60 4`
47
+
48
+
49
+![Multi Usec dB](doc/us_db.png)
50
+`python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db 84`
51
+
52
+![Usec dB Spread](doc/us_db_map.png)
53
+`python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db_map 84 72`
54
+
55
+![Min Max](doc/min_max_db.png)
56
+`python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max 36 48 60 72 84`
57
+
58
+
59
+![Min Max 2](doc/min_max_db_2.png)
60
+The last number in the list is the 'takeId'
61
+`python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max_2 36 48 60 72 84 2`
62
+
63
+`python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od manual_db`
64
+
65
+Interpolate across the min and max db values to form the min/max curves for the complete
66
+set of keys.  The anchor points for the curves are taken from cfg record
67
+manuMinD,manualAnchorPitchMinDbL, and manualMaxDbL.
68
+
69
+````
70
+      # select the event (takeId, eventIdx) to use to represent the min value for each pitch
71
+      manualMinD: {
72
+        36: [2, 10],
73
+        48: [2, 10],
74
+        60: [2, 10],
75
+        72: [2, 10],
76
+        84: [2, 10]
77
+        },
78
+
79
+      # leave 60 out of the min anchor point list
80
+      manualAnchorPitchMinDbL: [ 36,48,72,84 ],
81
+      
82
+      manualAnchorPitchMaxDbL: [ 36,48,60,72,84 ],
83
+
84
+````

BIN
do_td_plot.png Vedi File


BIN
doc/do_td_plot.png Vedi File


BIN
doc/manual_db.png Vedi File


BIN
doc/min_max_db_2.png Vedi File


BIN
doc/multi_plot.png Vedi File


BIN
doc/plot_spectral_ranges.png Vedi File


BIN
doc/us_db_map.png Vedi File


+ 142
- 16
p_ac.yml Vedi File

3
 
3
 
4
 
4
 
5
     # Audio device setup
5
     # Audio device setup
6
-    audio_off: {
7
-      inPortLabel: "5 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
6
+    audio: {
7
+      inPortLabel: "8 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
8
       outPortLabel: ,
8
       outPortLabel: ,
9
     },
9
     },
10
 
10
 
12
         inMonitorFl: False,
12
         inMonitorFl: False,
13
         outMonitorFl: False,
13
         outMonitorFl: False,
14
         throughFl: False,
14
         throughFl: False,
15
-        #inPortLabel: "Fastlane:Fastlane MIDI A",
16
-        #outPortLabel: "Fastlane:Fastlane MIDI A"
17
-        inPortLabel: "picadae:picadae MIDI 1",
18
-        outPortLabel: "picadae:picadae MIDI 1"
15
+        inPortLabel: "Fastlane:Fastlane MIDI A",
16
+        outPortLabel: "Fastlane:Fastlane MIDI A"
17
+        #inPortLabel: "picadae:picadae MIDI 1",
18
+        #outPortLabel: "picadae:picadae MIDI 1"
19
     },
19
     },
20
     
20
     
21
     # Picadae API args
21
     # Picadae API args
23
     serial_baud: 38400,
23
     serial_baud: 38400,
24
     i2c_base_addr: 21,
24
     i2c_base_addr: 21,
25
     prescaler_usec: 16,
25
     prescaler_usec: 16,
26
+    pwm_div: 5,
26
     serial_sync_timeout_ms: 10000,
27
     serial_sync_timeout_ms: 10000,
27
 
28
 
28
 
29
 
29
     # MeasureSeq args
30
     # MeasureSeq args
30
-    outDir: "~/temp/p_ac_3g",
31
+    outDir: "~/temp/p_ac_3_oe",
31
     noteDurMs: 500,
32
     noteDurMs: 500,
32
     pauseDurMs: 500,
33
     pauseDurMs: 500,
33
     reversePulseListFl: True,
34
     reversePulseListFl: True,
34
     useFullPulseListFl: True,
35
     useFullPulseListFl: True,
35
     maxSilentNoteCount: 4,
36
     maxSilentNoteCount: 4,
36
     silentNoteMaxPulseUs: 15000,
37
     silentNoteMaxPulseUs: 15000,
37
-    silentNoteMinDurMs: 250,
38
+    silentNoteMinDurMs: 180, #250,
38
 
39
 
39
     # Midi file player
40
     # Midi file player
40
     midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt",
41
     midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt",
57
 
58
 
58
     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 ],    
59
     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 ],    
59
     
60
     
61
+    # full_pulse9L was the last pulse list used on Matt's piano
62
+    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],
60
 
63
 
61
-    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],
62
-    
63
     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 ],    
64
     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 ],    
64
 
65
 
65
     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 ],
66
     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 ],
66
     
67
     
67
     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 ],
68
     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 ],
69
+
70
+    # pulse lists below this line were developed on Steinway D (11/14/20)
71
+    # 60
72
+    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 ],
73
+
74
+    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 ],
68
     
75
     
76
+    full_pulse15L: [ 8000, 9000,10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 ],
77
+
78
+    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 ],
79
+
80
+    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 ],
81
+
82
+    full_pulse18L: [  11500, 11625, 11750, 11875, 12000, 12250, 12500, 12750, 13000, 13500, 14000, 14500, 15000, 15500 ],
83
+
84
+    full_pulse19L: [  11875, 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875, 13000, 13250, 13500, 13750, 14000, 14500, 15000, 15500, 16000, 16500 ],
85
+
86
+    # pulse lists below this line were developed on Steinway D w/ new firmware (11/21/20)
87
+
88
+    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 ],
89
+
90
+    # 60,72
91
+    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 ],
92
+
93
+    # 48,36,85
94
+    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 ],
95
+
96
+    # 84
97
+    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  ],
98
+
69
     # RMS analysis args
99
     # RMS analysis args
70
     analysisArgs: {
100
     analysisArgs: {
71
       rmsAnalysisArgs: {
101
       rmsAnalysisArgs: {
77
         durDecayPct: 40,  # percent drop in RMS to indicate the end of a note
107
         durDecayPct: 40,  # percent drop in RMS to indicate the end of a note
78
       },
108
       },
79
 
109
 
80
-      resampleMinDb: 7.0,            # note's less than this will be skipped
110
+      resampleMinDb: -5.0,           # note's less than this will be skipped
81
       resampleNoiseLimitPct: 5.0,    # 
111
       resampleNoiseLimitPct: 5.0,    # 
82
-      resampleMinDurMs: 800,         # notes's whose duration is less than this will be skipped
83
-      
84
-      minAttkDb: 7.0,   # threshold of silence level 
112
+      resampleMinDurMs: 150,         # notes's whose duration is less than this will be skipped
113
+
114
+      useLastTakeOnlyFl: True,
115
+      minAttkDb: -5.0,   # threshold of silence level 
85
       maxDbOffset: 0.25, # travel down the from the max. note level by at most this amount to locate the max. peak
116
       maxDbOffset: 0.25, # travel down the from the max. note level by at most this amount to locate the max. peak
86
       maxDeltaDb: 1.5,  # maximum db change between volume samples (changes greater than this will trigger resampling)
117
       maxDeltaDb: 1.5,  # maximum db change between volume samples (changes greater than this will trigger resampling)
87
       samplesPerDb: 4,   # count of samples per dB to resample ranges whose range is less than maxDeltaDb
118
       samplesPerDb: 4,   # count of samples per dB to resample ranges whose range is less than maxDeltaDb
93
       },
124
       },
94
 
125
 
95
       manualMinD: {
126
       manualMinD: {
127
+        36: [2, 10],
128
+        48: [2, 10],
129
+        60: [2, 10],
130
+        72: [2, 10],
131
+        84: [2, 10]
132
+        },
133
+
134
+      manualAnchorPitchMinDbL: [ 36,48,72,84 ],
135
+      manualAnchorPitchMaxDbL: [ 36,48,60,72,84 ],
136
+        
137
+        
138
+      manualMinD_0: {
96
         23: [2, 24],
139
         23: [2, 24],
97
         24: [2, 18],
140
         24: [2, 18],
98
         25: [2, 41],
141
         25: [2, 41],
175
         
218
         
176
         },
219
         },
177
 
220
 
178
-      manualAnchorPitchMinDbL: [ 23, 27, 31, 34, 44, 51, 61, 70, 74, 81, 87, 93, 96, 101 ],
179
-      manualAnchorPitchMaxDbL: [ 23, 32, 49, 57, 67, 76, 83, 93, 99, 101 ],
221
+      manualAnchorPitchMinDbL_0: [ 23, 27, 31, 34, 44, 51, 61, 70, 74, 81, 87, 93, 96, 101 ],
222
+      manualAnchorPitchMaxDbL_0: [ 23, 32, 49, 57, 67, 76, 83, 93, 99, 101 ],
180
 
223
 
181
       calibrateArgs: {
224
       calibrateArgs: {
182
 
225
 
213
           dbSrcLabel: 'hm',              # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
256
           dbSrcLabel: 'hm',              # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
214
 
257
 
215
           holdDutyPctD:  {
258
           holdDutyPctD:  {
259
+          23: [[0, 40]],
260
+          24: [[0, 40]],
261
+          25: [[0, 40]],
262
+          26: [[0, 40]],
263
+          27: [[0, 40]],
264
+          28: [[0, 40]],
265
+          29: [[0, 40]],
266
+          30: [[0, 40]],
267
+          31: [[0, 40]],
268
+          32: [[0, 40]],
269
+          33: [[0, 40]],
270
+          34: [[0, 40]],
271
+          35: [[0, 40]],
272
+          36: [[0, 45]],
273
+          37: [[0, 40]],
274
+          38: [[0, 40]],
275
+          39: [[0, 40]],
276
+          40: [[0, 40]],
277
+          41: [[0, 40]],
278
+          42: [[0, 40]],
279
+          43: [[0, 40]],
280
+          44: [[0, 40]],
281
+          45: [[0, 40]],
282
+          46: [[0, 40]],
283
+          47: [[0, 40]],
284
+          48: [[0, 40]],
285
+          49: [[0, 40]],
286
+          50: [[0, 40]],
287
+          51: [[0, 40]],
288
+          52: [[0, 40]],
289
+          53: [[0, 40]],
290
+          54: [[0, 40]],
291
+          55: [[0, 40]],
292
+          56: [[0, 40]],
293
+          57: [[0, 40]],
294
+          58: [[0, 40]],
295
+          59: [[0, 40]],
296
+          60: [[0, 50]],
297
+          61: [[0, 43]],
298
+          62: [[0, 43]],
299
+          63: [[0, 43]],
300
+          64: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
301
+          65: [[0, 99]],              
302
+          66: [[0, 40]],
303
+          67: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
304
+          68: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
305
+          69: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
306
+          70: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
307
+          71: [[0, 40],[14000, 45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
308
+          72: [[0, 45],[11000, 65] ],
309
+          73: [[0, 45],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
310
+          74: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
311
+          75: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
312
+          76: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
313
+          77: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
314
+          78: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
315
+          79: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
316
+          80: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
317
+          81: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
318
+          82: [[0, 40],[14000,45],[15000,50],[16000,55],[17000,60],[18000,65],[19000,55]],
319
+          83: [[0, 42]],
320
+          84: [[0, 42],[10000,50],[11000,60]], 
321
+          85: [[0, 42],[9000,45],[10000,50],[11000,60]],
322
+          86: [[0, 40]],
323
+          87: [[0, 40]],
324
+          88: [[0, 40]],
325
+          89: [[0, 40]],
326
+          91: [[0, 40]],
327
+          92: [[0, 40]],
328
+          93: [[0, 40]],
329
+          94: [[0, 40]],
330
+          95: [[0, 40]],
331
+          96: [[0, 40]],
332
+          97: [[0, 40]],
333
+          98: [[0, 40]],
334
+          99: [[0, 40]],
335
+          100: [[0, 40]],
336
+          101: [[0, 40]],
337
+          106: [[0, 40]]
338
+          },
339
+
340
+          # Final for Matt's piano
341
+          holdDutyPct0D:  {
216
           23: [[0, 70]],
342
           23: [[0, 70]],
217
           24: [[0, 75]],
343
           24: [[0, 75]],
218
           25: [[0, 70]],
344
           25: [[0, 70]],

+ 137
- 63
plot_seq.py Vedi File

1
 ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org> 
1
 ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org> 
2
 ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
2
 ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
3
-import os, sys
3
+import os, sys,json
4
 import matplotlib.pyplot as plt
4
 import matplotlib.pyplot as plt
5
 import numpy as np
5
 import numpy as np
6
+from scipy.io import wavfile
6
 from common import parse_yaml_cfg
7
 from common import parse_yaml_cfg
7
 from rms_analysis import rms_analysis_main
8
 from rms_analysis import rms_analysis_main
8
 from rms_analysis import select_first_stable_note_by_delta_db
9
 from rms_analysis import select_first_stable_note_by_delta_db
9
 from rms_analysis import select_first_stable_note_by_dur
10
 from rms_analysis import select_first_stable_note_by_dur
10
 from rms_analysis import samples_to_linear_residual
11
 from rms_analysis import samples_to_linear_residual
11
 
12
 
13
+import rms_analysis as ra
14
+
15
+#from rms_analysis import audio_rms
16
+#from rms_analysis import locate_peak_indexes
17
+#from rms_analysis import audio_stft_rms
18
+#from rms_analysis import calc_harm_bins
19
+
12
 def is_nanV( xV ):
20
 def is_nanV( xV ):
13
     
21
     
14
     for i in range(xV.shape[0]):
22
     for i in range(xV.shape[0]):
31
 
39
 
32
 def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
40
 def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
33
 
41
 
42
+    
34
     # append the midi pitch to the input directory
43
     # append the midi pitch to the input directory
35
-    #inDir = os.path.join( inDir, "%i" % (midi_pitch))
44
+    inDir = os.path.join( inDir, str(midi_pitch))
36
 
45
 
37
     dirL =  os.listdir(inDir)
46
     dirL =  os.listdir(inDir)
38
 
47
 
39
     pkL = []
48
     pkL = []
40
 
49
 
50
+    maxTakeNumber = 0
51
+
41
     # for each take in this directory
52
     # for each take in this directory
42
     for idir in dirL:
53
     for idir in dirL:
43
 
54
 
46
 
57
 
47
         if not os.path.isfile(os.path.join( inDir,idir, "seq.json")):
58
         if not os.path.isfile(os.path.join( inDir,idir, "seq.json")):
48
             continue
59
             continue
60
+        
61
+        if analysisArgsD['useLastTakeOnlyFl']:
62
+            if take_number > maxTakeNumber:
63
+                pkL.clear()
64
+                maxTakeNumber = take_number
65
+            else:
66
+                continue
67
+
49
 
68
 
50
         # analyze this takes audio and locate the note peaks
69
         # analyze this takes audio and locate the note peaks
51
         r = rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
70
         r = rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
52
-
71
+            
53
         # store the peaks in pkL[ (db,us) ]
72
         # store the peaks in pkL[ (db,us) ]
54
         for db,us in zip(r.pkDbL,r.pkUsL):
73
         for db,us in zip(r.pkDbL,r.pkUsL):
55
             pkL.append( (db,us) )
74
             pkL.append( (db,us) )
68
     # locate the first and last note 
87
     # locate the first and last note 
69
     min_pk_idx, max_pk_idx = find_min_max_peak_index( pkDbL, analysisArgsD['minAttkDb'], analysisArgsD['maxDbOffset'] )
88
     min_pk_idx, max_pk_idx = find_min_max_peak_index( pkDbL, analysisArgsD['minAttkDb'], analysisArgsD['maxDbOffset'] )
70
 
89
 
90
+    print("MIN MAX:",min_pk_idx,pkUsL[min_pk_idx],max_pk_idx,pkUsL[max_pk_idx])
91
+
71
     db1 = pkDbL[ max_pk_idx ]
92
     db1 = pkDbL[ max_pk_idx ]
72
     db0 = pkDbL[ min_pk_idx ]
93
     db0 = pkDbL[ min_pk_idx ]
73
 
94
 
195
             
216
             
196
 
217
 
197
 
218
 
198
-def form_resample_pulse_time_list( inDir, analysisArgsD ):
219
+def form_resample_pulse_time_list( inDir, midi_pitch, analysisArgsD ):
199
     """" This function merges all available data from previous takes to form
220
     """" This function merges all available data from previous takes to form
200
     a new list of pulse times to sample.
221
     a new list of pulse times to sample.
201
     """
222
     """
202
 
223
 
203
-    # the last folder is always the midi pitch of the note under analysis
204
-    midi_pitch = int( inDir.split("/")[-1] )
205
-
224
+    inDir = os.path.join( inDir, str(midi_pitch) )
206
     dirL =  os.listdir(inDir)
225
     dirL =  os.listdir(inDir)
207
 
226
 
208
     pkL = []
227
     pkL = []
248
 
267
 
249
     ax.plot( pulseUsL, func(pulseUsL), color='red')
268
     ax.plot( pulseUsL, func(pulseUsL), color='red')
250
 
269
 
251
-def plot_resample_pulse_times_0( inDir, analysisArgsD ):
270
+def plot_resample_pulse_times_0( inDir, analysisArgsD, midi_pitch, printDir="" ):
252
 
271
 
253
-    newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
272
+    newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, midi_pitch, analysisArgsD )
254
 
273
 
255
-    midi_pitch = int( inDir.split("/")[-1] )
256
     velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
274
     velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
257
     
275
     
258
     fig,ax = plt.subplots()
276
     fig,ax = plt.subplots()
262
     for us in newPulseUsL:
280
     for us in newPulseUsL:
263
         ax.axvline( x = us )
281
         ax.axvline( x = us )
264
 
282
 
265
-    ax.plot(velTblUsL,velTblDbL,marker='.',linestyle='None')
283
+    print(len(velTblUsL))
284
+    ax.plot(velTblUsL,velTblDbL,marker='.',linestyle='None',color='red')
266
 
285
 
286
+    if printDir:
287
+        plt.savefig(os.path.join(printDir,"plot_resample_times_0.png"),format="png")
267
     
288
     
268
     plt.show()
289
     plt.show()
269
 
290
 
270
-def plot_resample_pulse_times( inDir, analysisArgsD ):
291
+def plot_resample_pulse_times( inDir, analysisArgsD, midi_pitch, printDir="" ):
271
 
292
 
272
-    newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
293
+    newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, midi_pitch, analysisArgsD )
273
 
294
 
274
-    midi_pitch = int( inDir.split("/")[-1] )
275
     velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
295
     velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
276
     
296
     
277
     fig,axL = plt.subplots(2,1,gridspec_kw={'height_ratios': [2, 1]})
297
     fig,axL = plt.subplots(2,1,gridspec_kw={'height_ratios': [2, 1]})
290
     axL[1].axhline(1.0,color='black')
310
     axL[1].axhline(1.0,color='black')
291
     axL[1].plot(pulseUsL,np.abs(scoreV * 100.0 / rmsDbV))
311
     axL[1].plot(pulseUsL,np.abs(scoreV * 100.0 / rmsDbV))
292
     axL[1].set_ylim((0.0,50))
312
     axL[1].set_ylim((0.0,50))
313
+
314
+    if printDir:
315
+        plt.savefig(os.path.join(printDir,"plot_resample_times.png"),format="png")
316
+
293
     plt.show()
317
     plt.show()
294
 
318
 
295
 
319
 
369
     """ Plot a single spectrum, 'specV' and the harmonic peak location boundaries."""
393
     """ Plot a single spectrum, 'specV' and the harmonic peak location boundaries."""
370
     
394
     
371
     binN      = specV.shape[0]
395
     binN      = specV.shape[0]
372
-    harmLBinL,harmMBinL,harmUBinL = calc_harm_bins( srate, binHz, midiPitch, harmN )
396
+    harmLBinL,harmMBinL,harmUBinL = ra.calc_harm_bins( srate, binHz, midiPitch, harmN )
373
 
397
 
374
     fundHz      = harmMBinL[0] * binHz
398
     fundHz      = harmMBinL[0] * binHz
375
     maxPlotHz   = fundHz * (harmN+1)
399
     maxPlotHz   = fundHz * (harmN+1)
386
         ax.axvline( x=h1 * binHz, color="black")
410
         ax.axvline( x=h1 * binHz, color="black")
387
         ax.axvline( x=h2 * binHz, color="blue")
411
         ax.axvline( x=h2 * binHz, color="blue")
388
 
412
 
389
-    ax.set_ylabel(str(midiPitch))
413
+    ax.set_ylabel("dB : %i " % (midiPitch))
390
         
414
         
391
-def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbLinRef=0.001 ):
392
-    """ Plot the spectrum from one note (7th from last) in each attack pulse length sequence referred to by pitchL."""
415
+def plot_spectral_ranges( inDir, pitchTakeL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbLinRef=0.001, printDir="" ):
416
+    """ Plot the spectrum from one note (7th from last) in each attack pulse length sequence referred to by pitchTakeL."""
393
     
417
     
394
-    plotN = len(pitchL)
418
+    plotN = len(pitchTakeL)
395
     fig,axL = plt.subplots(plotN,1)
419
     fig,axL = plt.subplots(plotN,1)
396
 
420
 
397
-    for plot_idx,midiPitch in enumerate(pitchL):
421
+    for plot_idx,(midiPitch,takeId) in enumerate(pitchTakeL):
398
 
422
 
399
         # get the audio and meta-data file names
423
         # get the audio and meta-data file names
400
-        seqFn   = os.path.join( inDir, str(midiPitch), "seq.json")
401
-        audioFn = os.path.join( inDir, str(midiPitch), "audio.wav")
424
+        seqFn   = os.path.join( inDir, str(midiPitch), str(takeId), "seq.json")
425
+        audioFn = os.path.join( inDir, str(midiPitch), str(takeId), "audio.wav")
402
 
426
 
403
         # read the meta data object
427
         # read the meta data object
404
         with open( seqFn, "rb") as f:
428
         with open( seqFn, "rb") as f:
405
             r = json.load(f)
429
             r = json.load(f)
406
-
430
+            
407
         # read the audio file
431
         # read the audio file
408
         srate, signalM  = wavfile.read(audioFn)
432
         srate, signalM  = wavfile.read(audioFn)
433
+
434
+
435
+        # convert the audio signal vector to contain only the first (left) channel
436
+        if len(signalM.shape)>1:
437
+            signalM = signalM[:,0].squeeze()
438
+        
409
         sigV  = signalM / float(0x7fff)
439
         sigV  = signalM / float(0x7fff)
440
+        
410
 
441
 
411
         # calc. the RMS envelope in the time domain
442
         # calc. the RMS envelope in the time domain
412
-        rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
443
+        rms0DbV, rms0_srate = ra.audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
413
 
444
 
414
         # locate the sample index of the peak of each note attack 
445
         # locate the sample index of the peak of each note attack 
415
-        pkIdx0L = locate_peak_indexes( rms0DbV, rms0_srate, r['eventTimeL'] )
446
+        pkIdx0L = ra.locate_peak_indexes( rms0DbV, rms0_srate, r['eventTimeL'] )
416
 
447
 
417
         # select the 7th to last note for spectrum measurement
448
         # select the 7th to last note for spectrum measurement
418
 
449
 
423
 
454
 
424
 
455
 
425
         # calc. the RMS envelope by taking the max spectral peak in each STFT window 
456
         # calc. the RMS envelope by taking the max spectral peak in each STFT window 
426
-        rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, spectrumSmpIdx)
457
+        rmsDbV, rms_srate, specV, specHopIdx, binHz = ra.audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, spectrumSmpIdx)
427
 
458
 
428
         # specV[] is the spectrum of the note at spectrumSmpIdx
459
         # specV[] is the spectrum of the note at spectrumSmpIdx
429
 
460
 
430
         # plot the spectrum and the harmonic selection ranges
461
         # plot the spectrum and the harmonic selection ranges
431
         plot_spectrum( axL[plot_idx], srate, binHz, specV, midiPitch, harmN )
462
         plot_spectrum( axL[plot_idx], srate, binHz, specV, midiPitch, harmN )
463
+
464
+    axL[-1].set_xlabel("Hertz")
465
+
466
+    if printDir:
467
+        plt.savefig(os.path.join(printDir,"plot_spectral_ranges.png"),format="png")
468
+    
432
     plt.show()
469
     plt.show()
433
 
470
 
434
         
471
         
435
         
472
         
436
 def td_plot( ax, inDir, midi_pitch, id, analysisArgsD ):
473
 def td_plot( ax, inDir, midi_pitch, id, analysisArgsD ):
437
 
474
 
475
+    # 
438
     r = rms_analysis_main( inDir, midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
476
     r = rms_analysis_main( inDir, midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
439
 
477
 
478
+    # find min/max peak in the sequence
440
     min_pk_idx, max_pk_idx = find_min_max_peak_index( r.pkDbL, analysisArgsD['minAttkDb'], analysisArgsD['maxDbOffset'] )    
479
     min_pk_idx, max_pk_idx = find_min_max_peak_index( r.pkDbL, analysisArgsD['minAttkDb'], analysisArgsD['maxDbOffset'] )    
441
-    
480
+
481
+    # find ranges of the sequence which should be skipped because they are noisy or unreliable
442
     skipPkIdxL = find_skip_peaks( r.rmsDbV, r.pkIdxL, min_pk_idx, max_pk_idx )
482
     skipPkIdxL = find_skip_peaks( r.rmsDbV, r.pkIdxL, min_pk_idx, max_pk_idx )
443
 
483
 
484
+    # find peaks whose difference to surrounding peaks is greater than 'maxDeltaDb'.
444
     jmpPkIdxL = find_out_of_range_peaks( r.rmsDbV, r.pkIdxL, min_pk_idx, max_pk_idx, analysisArgsD['maxDeltaDb'] )
485
     jmpPkIdxL = find_out_of_range_peaks( r.rmsDbV, r.pkIdxL, min_pk_idx, max_pk_idx, analysisArgsD['maxDeltaDb'] )
445
 
486
 
446
-    secV = np.arange(0,len(r.rmsDbV)) / r.rms_srate
447
     
487
     
448
-    ax.plot( secV, r.rmsDbV )
449
-    ax.plot( np.arange(0,len(r.tdRmsDbV)) / r.rms_srate, r.tdRmsDbV, color="black" )
488
+    secV = np.arange(0,len(r.rmsDbV)) / r.rms_srate
489
+
490
+    # plot the harmonic RMS signal
491
+    ax.plot( secV, r.rmsDbV, color='blue', label="Harmonic" )
492
+
493
+    # plot the time-domain RMS signal
494
+    ax.plot( np.arange(0,len(r.tdRmsDbV)) / r.rms_srate, r.tdRmsDbV, color="black", label="TD" )
450
 
495
 
451
     
496
     
452
-    # print beg/end boundaries
497
+    # print note beg/end/peak boundaries
453
     for i,(begMs, endMs) in enumerate(r.eventTimeL):
498
     for i,(begMs, endMs) in enumerate(r.eventTimeL):
454
 
499
 
455
         pkSec = r.pkIdxL[i] / r.rms_srate
500
         pkSec = r.pkIdxL[i] / r.rms_srate
472
             ax.plot( [pki / r.rms_srate], [ r.rmsDbV[pki] ], marker=6, color="blue")
517
             ax.plot( [pki / r.rms_srate], [ r.rmsDbV[pki] ], marker=6, color="blue")
473
 
518
 
474
 
519
 
520
+    ax.legend();
521
+    ax.set_ylabel("dB");
522
+            
475
     return r
523
     return r
476
 
524
 
477
 
525
 
478
 
526
 
479
-def do_td_plot( inDir, analysisArgs ):
527
+def do_td_plot( inDir, analysisArgs, midi_pitch, takeId, printDir="" ):
480
     
528
     
481
     fig,axL = plt.subplots(3,1)
529
     fig,axL = plt.subplots(3,1)
482
     fig.set_size_inches(18.5, 10.5, forward=True)
530
     fig.set_size_inches(18.5, 10.5, forward=True)
483
 
531
 
484
-    id         = int(inDir.split("/")[-1])
485
-    midi_pitch = int(inDir.split("/")[-2])
532
+    # parse the file name
533
+    inDir = os.path.join(inDir,str(midi_pitch),str(takeId));
486
 
534
 
487
-    r = td_plot(axL[0],inDir,midi_pitch,id,analysisArgs)
535
+    # plot the time domain signal
536
+    r = td_plot(axL[0],inDir,midi_pitch,takeId,analysisArgs)
488
 
537
 
489
     qualityV = np.array([ x.quality  for x in r.statsL ]) * np.max(r.pkDbL)
538
     qualityV = np.array([ x.quality  for x in r.statsL ]) * np.max(r.pkDbL)
490
     durMsV   = np.array([ x.durMs    for x in r.statsL ])
539
     durMsV   = np.array([ x.durMs    for x in r.statsL ])
491
     avgV     = np.array([ x.durAvgDb for x in r.statsL ])
540
     avgV     = np.array([ x.durAvgDb for x in r.statsL ])
492
     
541
     
493
-    #durMsV[ durMsV < 400 ] = 0
494
-    #durMsV = durMsV * np.max(r.pkDbL)/np.max(durMsV)
495
-    #durMsV  = durMsV / 100.0
496
 
542
 
497
     dV = np.diff(r.pkDbL) / r.pkDbL[1:] 
543
     dV = np.diff(r.pkDbL) / r.pkDbL[1:] 
498
     
544
     
499
-    axL[1].plot( r.pkUsL, r.pkDbL, marker='.',label="pkDb" )
500
-    axL[1].plot( r.pkUsL, qualityV, marker='.',label="quality" )
501
-    axL[1].plot( r.pkUsL, avgV,     marker='.',label="avgDb" )
545
+    axL[1].plot( r.pkUsL, r.pkDbL,  marker='.',  label="harmonic" )
546
+    #axL[1].plot( r.pkUsL, qualityV, marker='.', label="quality" )
547
+    axL[1].plot( r.pkUsL, avgV,     marker='.', label="harm-td avg" )
548
+    axL[1].legend()
549
+    axL[1].set_ylabel("dB");
550
+    
502
     #axL[2].plot( r.pkUsL, durMsV,   marker='.' )
551
     #axL[2].plot( r.pkUsL, durMsV,   marker='.' )
503
-    axL[2].plot( r.pkUsL[1:], dV,       marker='.',label='d')
552
+    axL[2].plot( r.pkUsL[1:], dV,       marker='.',label='delta')
504
     axL[2].set_ylim([-1,1])
553
     axL[2].set_ylim([-1,1])
505
-    axL[1].legend()
554
+    axL[2].legend()
555
+    axL[2].set_ylabel("dB");
506
 
556
 
557
+    axL[2].set_xlabel("Microseconds")
558
+    
507
 
559
 
508
     sni = select_first_stable_note_by_dur( durMsV )
560
     sni = select_first_stable_note_by_dur( durMsV )
509
     if sni is not None:
561
     if sni is not None:
519
 
571
 
520
     for i in range(1,len(r.pkUsL)):
572
     for i in range(1,len(r.pkUsL)):
521
         axL[2].text( r.pkUsL[i],  dV[i-1], "%i" % (i))
573
         axL[2].text( r.pkUsL[i],  dV[i-1], "%i" % (i))
522
-        
523
-    
524
-    plt.show()
525
 
574
 
526
-def do_td_multi_plot( inDir, analysisArgs ):
575
+    if printDir:
576
+        plt.savefig(os.path.join(printDir,"do_td_plot.png"),format="png")
577
+    plt.show()
527
 
578
 
528
-    midi_pitch = int(inDir.split("/")[-1])
579
+def do_td_multi_plot( inDir, analysisArgs, pitchTakeL, printPlotFl=False ):
529
 
580
 
530
-    dirL =  os.listdir(inDir)
581
+    #midi_pitch = int(inDir.split("/")[-1])
531
 
582
 
532
-    fig,axL = plt.subplots(len(dirL),1)
583
+    fig,axL = plt.subplots(len(pitchTakeL),1)
533
 
584
 
534
     
585
     
535
-    for id,(idir,ax) in enumerate(zip(dirL,axL)):
586
+    for id,((pitch,takeId),ax) in enumerate(zip(pitchTakeL,axL)):
536
 
587
 
537
-        td_plot(ax, os.path.join(inDir,str(id)), midi_pitch, id, analysisArgs )
538
-    
588
+        td_plot(ax, os.path.join(inDir,str(pitch),str(takeId)), pitch, takeId, analysisArgs )
589
+
590
+    ax.set_xlabel("Seconds")
591
+
592
+    if printDir:
593
+        plt.savefig(os.path.join(printDir,"multi_plot.png"),format="png")
539
     plt.show()
594
     plt.show()
540
 
595
 
541
     
596
     
542
 if __name__ == "__main__":
597
 if __name__ == "__main__":
543
 
598
 
544
-    inDir = sys.argv[1]
545
-    cfgFn = sys.argv[2]
546
-    take_id = None if len(sys.argv)<4 else sys.argv[3]
599
+    printDir = os.path.expanduser("~/src/picadae_ac_3/doc")
600
+    cfgFn = sys.argv[1]
601
+    inDir = sys.argv[2]
602
+    mode  = sys.argv[3]
547
 
603
 
548
     cfg = parse_yaml_cfg( cfgFn )
604
     cfg = parse_yaml_cfg( cfgFn )
605
+    
606
+    if mode == "td_plot":
607
+        
608
+        # python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_plot 60 10
609
+        pitch   = int(sys.argv[4])
610
+        take_id = int(sys.argv[5])
611
+        do_td_plot(inDir,cfg.analysisArgs, pitch, take_id, printDir )
549
 
612
 
550
-    if take_id is not None:
551
-        inDir = os.path.join(inDir,take_id)
552
-        do_td_plot(inDir,cfg.analysisArgs)
553
-    else:    
554
-        #do_td_multi_plot(inDir,cfg.analysisArgs)
613
+    elif mode == "td_multi_plot" or mode == 'plot_spectral_ranges':
555
 
614
 
556
-        #plot_spectral_ranges( inDir, [ 24, 36, 48, 60, 72, 84, 96, 104] )
615
+        pitchTakeIdL = []
616
+        for i in range(4,len(sys.argv),2):
617
+            pitchTakeIdL.append( (int(sys.argv[i]), int(sys.argv[i+1])) )
557
 
618
 
558
-        plot_resample_pulse_times( inDir, cfg.analysisArgs )
619
+        if mode == "td_multi_plot":
620
+            # python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 36 2 48 3 60 4
621
+            do_td_multi_plot(inDir, cfg.analysisArgs, pitchTakeIdL, printDir )
622
+        else:
623
+            # python plot_seq.py p_ac.yml ~/temp/p_ac_3_od plot_spectral_ranges 36 2 48 3 60 4
624
+            plot_spectral_ranges( inDir, pitchTakeIdL, printDir=printDir )
625
+        
626
+    elif mode == "resample_pulse_times":
559
 
627
 
628
+        # python plot_seq.py p_ac.yml ~/temp/p_ac_3_od  resample_pulse_times 60
629
+        pitch   = int(sys.argv[4])        
630
+        plot_resample_pulse_times( inDir, cfg.analysisArgs, pitch, printDir )
631
+        
632
+    else:
633
+        print("Unknown plot mode:%s" % (mode))

+ 103
- 59
plot_seq_1.py Vedi File

14
     dur_outL = []
14
     dur_outL = []
15
     tid_outL = []
15
     tid_outL = []
16
 
16
 
17
+
17
     dbL,usL,durMsL,takeIdL = tuple(zip(*pkL))
18
     dbL,usL,durMsL,takeIdL = tuple(zip(*pkL))
18
-    
19
+
19
     us_refL,db_refL,dur_refL = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==refTakeId])
20
     us_refL,db_refL,dur_refL = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==refTakeId])
20
 
21
 
21
     
22
     
26
         if takeId == refTakeId:
27
         if takeId == refTakeId:
27
             db_outL += db0L
28
             db_outL += db0L
28
         else:
29
         else:
29
-            db1V = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
30
-            db_outL += db1V.tolist()
30
+            db1V     = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
31
+
32
+            if db1V is not None:
33
+                db_outL += db1V.tolist()
31
 
34
 
32
         us_outL += us0L
35
         us_outL += us0L
33
         dur_outL+= dur0L
36
         dur_outL+= dur0L
37
 
40
 
38
 
41
 
39
 def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
42
 def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
40
-    
43
+
41
     inDir = os.path.join(inDir,"%i" % (midi_pitch))
44
     inDir = os.path.join(inDir,"%i" % (midi_pitch))
42
 
45
 
43
     takeDirL =  os.listdir(inDir)
46
     takeDirL =  os.listdir(inDir)
44
 
47
 
45
     pkL = []
48
     pkL = []
46
 
49
 
50
+    refTakeId = None
47
     usRefL = None
51
     usRefL = None
48
     dbRefL = None
52
     dbRefL = None
49
 
53
 
50
     # for each take in this directory
54
     # for each take in this directory
51
-    for take_number in range(len(takeDirL)):
55
+    for take_folder in takeDirL:
56
+
57
+        take_number = int(take_folder)
58
+
59
+        if refTakeId is None:
60
+            refTakeId = take_number
52
 
61
 
53
         # analyze this takes audio and locate the note peaks
62
         # analyze this takes audio and locate the note peaks
54
         r = rms_analysis.rms_analysis_main( os.path.join(inDir,str(take_number)), midi_pitch, **analysisArgsD )
63
         r = rms_analysis.rms_analysis_main( os.path.join(inDir,str(take_number)), midi_pitch, **analysisArgsD )
57
         for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
66
         for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
58
             pkL.append( (db,us,stats.durMs,take_number) )
67
             pkL.append( (db,us,stats.durMs,take_number) )
59
 
68
 
60
-    pkL = fit_to_reference( pkL, 0 )
69
+
70
+    pkUsL        = []
71
+    pkDbL        = []
72
+    durMsL       = []
73
+    takeIdL      = []
74
+    holdDutyPctL = []
75
+    
76
+    if refTakeId is None:
77
+        print("No valid data files at %s pitch:%i" % (inDir,midi_pitch))
78
+    else:
79
+        
80
+        pkL = fit_to_reference( pkL, refTakeId )
61
     
81
     
62
-    # sort the peaks on increasing attack pulse microseconds
63
-    pkL = sorted( pkL, key= lambda x: x[1] )
82
+        # sort the peaks on increasing attack pulse microseconds
83
+        pkL = sorted( pkL, key= lambda x: x[1] )
64
 
84
 
65
-    # merge sample points that separated by less than 'minSampleDistUs' milliseconds
66
-    #pkL = merge_close_sample_points( pkL, analysisArgsD['minSampleDistUs'] )
85
+        # merge sample points that separated by less than 'minSampleDistUs' milliseconds
86
+        #pkL = merge_close_sample_points( pkL, analysisArgsD['minSampleDistUs'] )
67
     
87
     
68
-    # split pkL 
69
-    pkDbL,pkUsL,durMsL,takeIdL = tuple(zip(*pkL))
88
+        # split pkL 
89
+        pkDbL,pkUsL,durMsL,takeIdL = tuple(zip(*pkL))
70
 
90
 
71
     return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
91
     return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
72
 
92
 
208
     return reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx
228
     return reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx
209
 
229
 
210
 def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
230
 def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
211
-
231
+    
212
     usL, dbL, durMsL,_,_ = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
232
     usL, dbL, durMsL,_,_ = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
213
 
233
 
214
     reUsL,_,_,_,_,_,_ = get_resample_points( usL, dbL, durMsL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
234
     reUsL,_,_,_,_,_,_ = get_resample_points( usL, dbL, durMsL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
220
 def plot_us_db_curves( ax, inDir, keyMapD, midi_pitch, analysisArgsD, plotResamplePointsFl=False, plotTakesFl=True, usMax=None ):
240
 def plot_us_db_curves( ax, inDir, keyMapD, midi_pitch, analysisArgsD, plotResamplePointsFl=False, plotTakesFl=True, usMax=None ):
221
 
241
 
222
     usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
242
     usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
243
+
223
     reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx  = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
244
     reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx  = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
224
 
245
 
225
     # plot first audible and non-skip position
246
     # plot first audible and non-skip position
303
     
324
     
304
     ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
325
     ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
305
     
326
     
306
-def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None ):
327
+def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None, printDir="" ):
307
 
328
 
308
     analysisArgsD = cfg.analysisArgs
329
     analysisArgsD = cfg.analysisArgs
309
-    keyMapD = { d['midi']:d for d in cfg.key_mapL }
310
-    axN = len(pitchL)
311
-    fig,axL = plt.subplots(axN,1,sharex=True)
330
+    keyMapD       = { d['midi']:d for d in cfg.key_mapL }
331
+    axN           = len(pitchL)
332
+    fig,axL       = plt.subplots(axN,1,sharex=True)
312
     if axN == 1:
333
     if axN == 1:
313
         axL = [axL]
334
         axL = [axL]
335
+        
314
     fig.set_size_inches(18.5, 10.5*axN)
336
     fig.set_size_inches(18.5, 10.5*axN)
315
 
337
 
316
     for ax,midi_pitch in zip(axL,pitchL):
338
     for ax,midi_pitch in zip(axL,pitchL):
318
 
340
 
319
     if plotTakesFl:
341
     if plotTakesFl:
320
         plt.legend()
342
         plt.legend()
343
+
344
+    if printDir:
345
+        plt.savefig(os.path.join(printDir,"us_db.png"),format="png")
321
         
346
         
322
     plt.show()
347
     plt.show()
323
 
348
 
332
 
357
 
333
     for midi_pitch in pitchL:
358
     for midi_pitch in pitchL:
334
 
359
 
335
-        print(midi_pitch)
336
-        
337
         usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
360
         usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
338
 
361
 
339
         scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
362
         scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
340
         
363
         
341
         minDurMs      = cfg.analysisArgs['resampleMinDurMs']
364
         minDurMs      = cfg.analysisArgs['resampleMinDurMs']
342
-        minDb         = cfg.analysisArgs['resampleMinDb'],
365
+        minDb         = cfg.analysisArgs['resampleMinDb']
343
         noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
366
         noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
344
 
367
 
368
+        
369
+
345
         skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, scoreV.tolist(), takeIdL, minDurMs, minDb, noiseLimitPct )
370
         skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, scoreV.tolist(), takeIdL, minDurMs, minDb, noiseLimitPct )
346
     
371
     
347
 
372
 
364
     plt.legend()
389
     plt.legend()
365
     plt.show()
390
     plt.show()
366
 
391
 
367
-def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
392
+def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
368
 
393
 
369
     pitchFolderL = os.listdir(inDir)
394
     pitchFolderL = os.listdir(inDir)
370
-    
395
+
396
+    print(pitchL)
397
+
371
     if pitchL is None:
398
     if pitchL is None:
372
         pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
399
         pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
373
 
400
 
374
-
375
-    okL   = []
401
+    print(pitchL)
402
+        
403
+    okL       = []
376
     outPitchL = []
404
     outPitchL = []
377
-    minDbL = []
378
-    maxDbL = []
405
+    minDbL    = []
406
+    maxDbL    = []
407
+    
379
     for midi_pitch in pitchL:
408
     for midi_pitch in pitchL:
380
 
409
 
381
         print(midi_pitch)
410
         print(midi_pitch)
384
 
413
 
385
         okL.append(False)
414
         okL.append(False)
386
 
415
 
387
-        takeId = len(set(takeIdL))-1
388
-
389
         db_maxL = sorted(dbL)
416
         db_maxL = sorted(dbL)
390
         maxDbL.append( np.mean(db_maxL[-5:]) )
417
         maxDbL.append( np.mean(db_maxL[-5:]) )
391
-        
392
 
418
 
393
         usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId  ])
419
         usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId  ])
394
         
420
         
414
         ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
440
         ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
415
         ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
441
         ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
416
 
442
 
417
-
443
+ 
444
+    if printDir:
445
+        plt.savefig(os.path.join(printDir,"min_max_db_2.png"),format="png")
446
+    
447
+       
418
     plt.show()
448
     plt.show()
419
 
449
 
420
-def plot_min_db_manual( inDir, cfg ):
450
+def plot_min_db_manual( inDir, cfg, printDir=None ):
421
     
451
     
422
     pitchL     = list(cfg.manualMinD.keys())
452
     pitchL     = list(cfg.manualMinD.keys())
423
     
453
     
463
 
493
 
464
         if midi_pitch in cfg.manualAnchorPitchMaxDbL:
494
         if midi_pitch in cfg.manualAnchorPitchMaxDbL:
465
             anchorMaxDbL.append( max_db )
495
             anchorMaxDbL.append( max_db )
466
-            
496
+
467
         
497
         
498
+            
499
+    
468
 
500
 
469
     # Form the complete set of min/max db levels for each pitch by interpolating the
501
     # Form the complete set of min/max db levels for each pitch by interpolating the
470
     # db values between the manually selected anchor points.
502
     # db values between the manually selected anchor points.
491
     with open("minInterpDb.json",'w') as f:
523
     with open("minInterpDb.json",'w') as f:
492
         json.dump( { "pitchL":pitchL, "minDbL":list(interpMinDbL), "maxDbL":list(interpMaxDbL) }, f )
524
         json.dump( { "pitchL":pitchL, "minDbL":list(interpMinDbL), "maxDbL":list(interpMaxDbL) }, f )
493
 
525
 
494
-            
495
-            
496
-            
526
+                      
497
               
527
               
498
-
528
+    if printDir:
529
+        plt.savefig(os.path.join(printDir,"manual_db.png"),format="png")
499
 
530
 
500
     plt.show()
531
     plt.show()
501
     
532
     
502
-def plot_min_max_db( inDir, cfg, pitchL=None ):
533
+def plot_min_max_db( inDir, cfg, pitchL=None, printDir=None ):
503
 
534
 
504
     pitchFolderL = os.listdir(inDir)
535
     pitchFolderL = os.listdir(inDir)
505
     
536
     
518
         scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
549
         scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
519
 
550
 
520
         minDurMs      = cfg.analysisArgs['resampleMinDurMs']
551
         minDurMs      = cfg.analysisArgs['resampleMinDurMs']
521
-        minDb         = cfg.analysisArgs['resampleMinDb'],
552
+        minDb         = cfg.analysisArgs['resampleMinDb']
522
         noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
553
         noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
523
 
554
 
524
         skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )        
555
         skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )        
548
 
579
 
549
         ax.text( pitch, db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']))
580
         ax.text( pitch, db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']))
550
 
581
 
582
+    if printDir:
583
+        plt.savefig(os.path.join(printDir,"min_max_db.png"),format="png")
584
+    
551
 
585
 
552
     plt.show()
586
     plt.show()
553
     
587
     
575
         scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
609
         scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
576
         
610
         
577
         minDurMs      = cfg.analysisArgs['resampleMinDurMs']
611
         minDurMs      = cfg.analysisArgs['resampleMinDurMs']
578
-        minDb         = cfg.analysisArgs['resampleMinDb'],
612
+        minDb         = cfg.analysisArgs['resampleMinDb']
579
         noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
613
         noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
580
 
614
 
581
         # get the set of samples that are not valid (too short, too quiet, too noisy)
615
         # get the set of samples that are not valid (too short, too quiet, too noisy)
633
             
667
             
634
     return mapD, list(dbS)
668
     return mapD, list(dbS)
635
 
669
 
636
-def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0, pitchL=None ):
670
+def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0, pitchL=None, printDir=None ):
637
 
671
 
638
     fig,ax = plt.subplots()
672
     fig,ax = plt.subplots()
639
     
673
     
644
 
678
 
645
         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 ]
679
         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 ]
646
 
680
 
647
-        # get the us/db lists for this pitch
648
-        usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
681
+        if u_dL:
682
+        
683
+            # get the us/db lists for this pitch
684
+            usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
649
 
685
 
650
-        # plot central curve and std dev's
651
-        p = ax.plot(usL,dbL, marker='.', label=str(pitch))
652
-        ax.plot(uscL,dbL, marker='x', label=str(pitch), color=p[0].get_color(), linestyle='None')
653
-        ax.plot(usL,np.array(dbL)+dbsL, color=p[0].get_color(), alpha=0.3)
654
-        ax.plot(usL,np.array(dbL)-dbsL, color=p[0].get_color(), alpha=0.3)
686
+            # plot central curve and std dev's
687
+            p = ax.plot(usL,dbL, marker='.', label=str(pitch))
688
+            ax.plot(uscL,dbL, marker='x', label=str(pitch), color=p[0].get_color(), linestyle='None')
689
+            ax.plot(usL,np.array(dbL)+dbsL, color=p[0].get_color(), alpha=0.3)
690
+            ax.plot(usL,np.array(dbL)-dbsL, color=p[0].get_color(), alpha=0.3)
655
 
691
 
656
-        # plot us error bars
657
-        for db,us,uss,us_min,us_max in zip(dbL,usL,ussL,usnL,usxL):
658
-            ax.plot([us_min,us_max],[db,db], color=p[0].get_color(), alpha=0.3 )
659
-            ax.plot([us-uss,us+uss],[db,db], color=p[0].get_color(), alpha=0.3, marker='.', linestyle='None' )
692
+            # plot us error bars
693
+            for db,us,uss,us_min,us_max in zip(dbL,usL,ussL,usnL,usxL):
694
+                ax.plot([us_min,us_max],[db,db], color=p[0].get_color(), alpha=0.3 )
695
+                ax.plot([us-uss,us+uss],[db,db], color=p[0].get_color(), alpha=0.3, marker='.', linestyle='None' )
660
 
696
 
661
                     
697
                     
662
     plt.legend()
698
     plt.legend()
699
+
700
+    if printDir:
701
+        plt.savefig(os.path.join(printDir,"us_db_map.png"),format="png")
702
+    
663
     plt.show()
703
     plt.show()
664
 
704
 
665
 def report_take_ids( inDir ):
705
 def report_take_ids( inDir ):
760
     
800
     
761
 if __name__ == "__main__":
801
 if __name__ == "__main__":
762
 
802
 
763
-    inDir = sys.argv[1]
764
-    cfgFn = sys.argv[2]
765
-    mode  = sys.argv[3]
803
+    printDir =os.path.expanduser( "~/src/picadae_ac_3/doc")
804
+    cfgFn   = sys.argv[1]
805
+    inDir   = sys.argv[2]
806
+    mode    = sys.argv[3]
807
+    
766
     if len(sys.argv) <= 4:
808
     if len(sys.argv) <= 4:
767
         pitchL = None
809
         pitchL = None
768
     else:
810
     else:
771
     cfg = parse_yaml_cfg( cfgFn )
813
     cfg = parse_yaml_cfg( cfgFn )
772
 
814
 
773
     if mode == 'us_db':
815
     if mode == 'us_db':
774
-        plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True,usMax=None )
816
+        plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True,usMax=None, printDir=printDir )
775
     elif mode == 'noise':
817
     elif mode == 'noise':
776
         plot_all_noise_curves( inDir, cfg, pitchL )
818
         plot_all_noise_curves( inDir, cfg, pitchL )
777
     elif mode == 'min_max':
819
     elif mode == 'min_max':
778
-        plot_min_max_db( inDir, cfg, pitchL )
820
+        plot_min_max_db( inDir, cfg, pitchL, printDir=printDir )
779
     elif mode == 'min_max_2':
821
     elif mode == 'min_max_2':
780
-        plot_min_max_2_db( inDir, cfg, pitchL )
822
+        takeId = pitchL[-1]
823
+        del pitchL[-1]
824
+        plot_min_max_2_db( inDir, cfg, pitchL, takeId=takeId, printDir=printDir )
781
     elif mode == 'us_db_map':
825
     elif mode == 'us_db_map':
782
-        plot_us_to_db_map( inDir, cfg, pitchL=pitchL )
826
+        plot_us_to_db_map( inDir, cfg, pitchL=pitchL, printDir=printDir )
783
     elif mode == 'audacity':
827
     elif mode == 'audacity':
784
         rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )
828
         rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )
785
     elif mode == 'rpt_take_ids':
829
     elif mode == 'rpt_take_ids':
786
         report_take_ids( inDir )
830
         report_take_ids( inDir )
787
     elif mode == 'manual_db':
831
     elif mode == 'manual_db':
788
-        plot_min_db_manual( inDir, cfg )
832
+        plot_min_db_manual( inDir, cfg, printDir=printDir )
789
     elif mode == 'gen_vel_map':
833
     elif mode == 'gen_vel_map':
790
         gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" )
834
         gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" )
791
     elif mode == 'cache_us_db':
835
     elif mode == 'cache_us_db':

+ 62
- 17
rms_analysis.py Vedi File

201
     return None
201
     return None
202
 
202
 
203
 def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
203
 def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
204
-    
204
+    """ Collect some statistics and markers for each note in the sequence. """
205
     statsL = []
205
     statsL = []
206
 
206
 
207
     srate = r.rms_srate
207
     srate = r.rms_srate
208
 
208
 
209
     qmax = 0
209
     qmax = 0
210
+
211
+    
210
     
212
     
211
     for i,(begSmpMs, endSmpMs) in enumerate(r.eventTimeL):
213
     for i,(begSmpMs, endSmpMs) in enumerate(r.eventTimeL):
212
 
214
 
214
         endSmpIdx = int(round(srate * (endSmpMs + extraDurSearchMs) / 1000.0))
216
         endSmpIdx = int(round(srate * (endSmpMs + extraDurSearchMs) / 1000.0))
215
         pkSmpIdx  = r.pkIdxL[i]
217
         pkSmpIdx  = r.pkIdxL[i]
216
 
218
 
219
+        
217
         durMs = measure_duration_ms( r.rmsDbV, srate, pkSmpIdx, endSmpIdx, decay_pct )
220
         durMs = measure_duration_ms( r.rmsDbV, srate, pkSmpIdx, endSmpIdx, decay_pct )
218
 
221
 
219
         bi = pkSmpIdx
222
         bi = pkSmpIdx
232
             hmRmsDb_u = 0.0 if ei >= len(r.rmsDbV)   else np.mean(r.rmsDbV[bi:ei])
235
             hmRmsDb_u = 0.0 if ei >= len(r.rmsDbV)   else np.mean(r.rmsDbV[bi:ei])
233
             durAvgDb = (hmRmsDb_u + tdRmsDb_u)/2.0
236
             durAvgDb = (hmRmsDb_u + tdRmsDb_u)/2.0
234
 
237
 
235
-        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 }))
238
+        statsL.append( types.SimpleNamespace(**{
239
+            'begSmpSec':begSmpIdx/srate,
240
+            'endSmpSec':endSmpIdx/srate,
241
+            'pkSmpSec':pkSmpIdx/srate,
242
+            'durMs':durMs,
243
+            'pkDb':r.pkDbL[i],
244
+            'pulse_us':r.pkUsL[i],
245
+            'quality':qualityCoeff,
246
+            'durAvgDb':durAvgDb }))
236
 
247
 
237
     for i,r in enumerate(statsL):
248
     for i,r in enumerate(statsL):
238
         statsL[i].quality = 0 if qmax <= 0 else statsL[i].quality / qmax
249
         statsL[i].quality = 0 if qmax <= 0 else statsL[i].quality / qmax
243
         
254
         
244
         
255
         
245
                
256
                
246
-def locate_peak_indexes( xV, xV_srate, eventMsL ):
257
+def locate_peak_indexes( xV, xV_srate, eventMsL, audioFn ):
247
 
258
 
248
     pkIdxL = []
259
     pkIdxL = []
249
-    for begMs, endMs in eventMsL:
260
+    for i, (begMs, endMs) in enumerate(eventMsL):
250
 
261
 
251
         begSmpIdx = int(begMs * xV_srate / 1000.0)
262
         begSmpIdx = int(begMs * xV_srate / 1000.0)
252
         endSmpIdx = int(endMs * xV_srate / 1000.0)
263
         endSmpIdx = int(endMs * xV_srate / 1000.0)
253
 
264
 
254
-        pkIdxL.append( begSmpIdx + np.argmax( xV[begSmpIdx:endSmpIdx] ) )
265
+        if endSmpIdx != begSmpIdx:
266
+            pkIdxL.append( begSmpIdx + np.argmax( xV[begSmpIdx:endSmpIdx] ) )
267
+        else:
268
+            print("Missing peak %i : begween beg:%i ms end:%i ms : %s" % (i, begMs, endMs, audioFn ))
269
+            pkIdxL.append( None )
255
 
270
 
256
     return pkIdxL
271
     return pkIdxL
257
 
272
 
304
 
319
 
305
         sigV = buf_result.value
320
         sigV = buf_result.value
306
 
321
 
322
+        if len(sigV.shape) > 1:
323
+            sigV = sigV[:,0].squeeze()
324
+        
325
+        
326
+
307
         # get the annotated begin and end of the note as sample indexes into sigV
327
         # get the annotated begin and end of the note as sample indexes into sigV
308
         bi = int(round(annBegMs * audioDev.srate / 1000))
328
         bi = int(round(annBegMs * audioDev.srate / 1000))
309
         ei = int(round(annEndMs * audioDev.srate / 1000))
329
         ei = int(round(annEndMs * audioDev.srate / 1000))
399
 
419
 
400
 def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
420
 def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
401
 
421
 
422
+    # form the audio and meta data file names
402
     seqFn     = os.path.join( inDir, "seq.json")
423
     seqFn     = os.path.join( inDir, "seq.json")
403
     audioFn   = os.path.join( inDir, "audio.wav")
424
     audioFn   = os.path.join( inDir, "audio.wav")
404
     
425
     
405
-
426
+    # read the meta data file
406
     with open( seqFn, "rb") as f:
427
     with open( seqFn, "rb") as f:
407
         r = json.load(f)
428
         r = json.load(f)
408
     
429
     
409
-    
430
+    # rad the auido file
410
     srate, signalM  = wavfile.read(audioFn)
431
     srate, signalM  = wavfile.read(audioFn)
432
+
433
+    # convert the audio signal vector to contain only the first (left) channel
434
+    if len(signalM.shape)>1:
435
+        signalM = signalM[:,0].squeeze()
436
+
437
+    # convert the auido file to floats in range [-1.0 to 1.0]
411
     sigV  = signalM / float(0x7fff)
438
     sigV  = signalM / float(0x7fff)
412
 
439
 
440
+    # calc. the RMS signal
413
     tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
441
     tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
414
 
442
 
415
-    tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate,  r['eventTimeL'])
416
-    
443
+    # locate the peaks in the RMS signal
444
+    tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate,  r['eventTimeL'], audioFn )
445
+
446
+    # sum the first harmN harmonics to form a envelope of the audio signal (this is an alternative to the RMS signal)
417
     rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN  )
447
     rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN  )
418
 
448
 
419
-    pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'] )    
449
+    # locate the peaks in the harmonic sum signal
450
+    pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'], audioFn )    
420
 
451
 
452
+    # form the 'holdDutyPctlL' hold duty cycle transition point list
421
     holdDutyPctL = None
453
     holdDutyPctL = None
422
     if 'holdDutyPct' in r:
454
     if 'holdDutyPct' in r:
423
         holdDutyPctL = [ (0, r['holdDutyPct']) ]
455
         holdDutyPctL = [ (0, r['holdDutyPct']) ]
424
     else:
456
     else:
425
         holdDutyPctL = r['holdDutyPctL']
457
         holdDutyPctL = r['holdDutyPctL']
426
 
458
 
427
-
459
+    eventN = len(r['eventTimeL'])
460
+    assert( len(tdPkIdxL) == eventN and len(pkIdxL) == eventN )
461
+        
462
+    # filter out all missing events that have no peak
463
+    flL        = [ (tdPkIdxL[i] is not None) and  (pkIdxL[i] is not None)  for i in range(eventN) ]
464
+    eventTimeL = [ r['eventTimeL'][i]       for i in range(eventN) if  flL[i] ]
465
+    tdPkDbL    = [ tdRmsDbV[tdPkIdxL[i]]    for i in range(eventN) if  flL[i] ]
466
+    pkDbL      = [ rmsDbV[ pkIdxL[i]]       for i in range(eventN) if  flL[i] ]
467
+    #pkUsL      = [ r['pulseUsL'][pkIdxL[i]] for i in range(eventN) if  flL[i] ]
468
+    tdPkIdxL   = [ i                        for i in tdPkIdxL  if i is not None ]
469
+    pkIdxL     = [ i                        for i in pkIdxL    if i is not None ]
470
+    
471
+    # form the result record
428
     r = types.SimpleNamespace(**{
472
     r = types.SimpleNamespace(**{
429
         "audio_srate":srate,
473
         "audio_srate":srate,
430
-        "eventTimeMsL":r['eventTimeL'],
474
+        "eventTimeMsL": eventTimeL, #r['eventTimeL'],
431
         "tdRmsDbV": tdRmsDbV,
475
         "tdRmsDbV": tdRmsDbV,
432
         "tdPkIdxL": tdPkIdxL,
476
         "tdPkIdxL": tdPkIdxL,
433
-        "tdPkDbL": [ tdRmsDbV[i] for i in tdPkIdxL ],
477
+        "tdPkDbL": tdPkDbL,         # [ tdRmsDbV[i] for i in tdPkIdxL ],
434
         "binHz": binHz,
478
         "binHz": binHz,
435
-        "rmsDbV":rmsDbV,
479
+        "rmsDbV": rmsDbV,
436
         "rms_srate":rms_srate,
480
         "rms_srate":rms_srate,
437
         "pkIdxL":pkIdxL,            # pkIdxL[ len(pulsUsL) ] - indexes into rmsDbV[] of peaks
481
         "pkIdxL":pkIdxL,            # pkIdxL[ len(pulsUsL) ] - indexes into rmsDbV[] of peaks
438
-        "eventTimeL":r['eventTimeL'],
482
+        "eventTimeL": eventTimeL, #r['eventTimeL'],
439
         "holdDutyPctL":holdDutyPctL,
483
         "holdDutyPctL":holdDutyPctL,
440
-        'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ],
441
-        'pkUsL':r['pulseUsL'] })
484
+        'pkDbL': pkDbL, # [ rmsDbV[ i ] for i in pkIdxL ],
485
+        'pkUsL': r['pulseUsL'] })  # r['pulseUsL']
442
 
486
 
487
+    # 
443
     statsL = note_stats(r,durDecayPct)
488
     statsL = note_stats(r,durDecayPct)
444
 
489
 
445
     setattr(r,"statsL",   statsL )
490
     setattr(r,"statsL",   statsL )

Loading…
Annulla
Salva