9 Commits

Author SHA1 Message Date
  kevin 7119fe471b rms_analysis.py : Don't crash in write_audacity_label_files() when seq.json does not exist. 3 years ago
  kevin f36e7c8a64 plot_seq_1.py : Many changes and updates. 3 years ago
  kevin e32f224d12 elbow.py : In find_elbow() change default 'pointsPerLine' to 5. 3 years ago
  kevin 109944f130 README.md : Update formatting. 3 years ago
  kevin 6b845bd1b3 MidiDevice.py : get_input() now loops until all available messages are received. 3 years ago
  kevin 55695164b5 p_ac.py, p_ac.yml : Added use of VelTablePlayer,NoteTester,PolyNoteTester,ChordTester 3 years ago
  kevin ca030abcf5 ChordTester.py,NoteTester.py,PolyNoteTester.py,VelTablePlayer.py : iniital commit. 3 years ago
  kevin d96bc2fded .gitignore : ignore *.ipynb files. 3 years ago
  kevin 9374bcee0c Delete accidentallly tracked *.ipynb files 3 years ago
15 changed files with 1065 additions and 203 deletions
  1. 2
    1
      .gitignore
  2. 68
    0
      ChordTester.py
  3. 5
    3
      MidiDevice.py
  4. 119
    0
      NoteTester.py
  5. 148
    0
      PolyNoteTester.py
  6. 23
    16
      README.md
  7. 148
    0
      VelTablePlayer.py
  8. 2
    2
      elbow.py
  9. 138
    33
      p_ac.py
  10. 261
    98
      p_ac.yml
  11. 1
    1
      plot_all_note_durations.ipynb
  12. 1
    1
      plot_calibrate.ipynb
  13. 144
    46
      plot_seq_1.py
  14. 1
    1
      plot_us_db_range.ipynb
  15. 4
    1
      rms_analysis.py

+ 2
- 1
.gitignore View File

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

+ 68
- 0
ChordTester.py View File

@@ -0,0 +1,68 @@
1
+import types
2
+import time
3
+
4
+class ChordTester:
5
+    def __init__( self, cfg, api ):
6
+        self.api = api
7
+        self.cfg = types.SimpleNamespace(**cfg.ChordTester)
8
+
9
+        self.nextMs       = 0
10
+        self.isStartedFl  = False
11
+        self.curNoteCnt   = 0
12
+        self.curRepeatCnt = 0
13
+        self.isPlayingFl  = False
14
+
15
+
16
+    def start( self ):
17
+        self.api.set_hold_duty_all( self.cfg.holdDuty )
18
+        if self.cfg.useVelTableFl:
19
+            self.api.set_vel_table_all( self.cfg.pitchL )
20
+        self.curNoteCnt = 0
21
+        self.curRepeatCnt = 0
22
+        self.isStartedFl = True
23
+
24
+    def stop( self ):
25
+        self.isStartedFl = False
26
+        self.api.all_notes_off()
27
+        
28
+
29
+
30
+    def tick( self, ms ):
31
+
32
+        if self.isStartedFl and ms >= self.nextMs:
33
+
34
+            if self.isPlayingFl:
35
+
36
+                # turn notes off
37
+                for i in range(0,self.curNoteCnt+1):
38
+                    self.api.note_off( self.cfg.pitchL[i])
39
+                    time.sleep( 0.01 )
40
+
41
+                # repeat or advance the chord note count
42
+                self.curRepeatCnt += 1
43
+                if self.curRepeatCnt >= self.cfg.repeatCnt:
44
+                    self.curRepeatCnt = 0
45
+                    self.curNoteCnt  += 1
46
+                    if self.curNoteCnt >= len(self.cfg.pitchL):
47
+                        self.isStartedFl = False
48
+                        self.curNoteCnt = 0
49
+                        
50
+                self.isPlayingFl = False
51
+                self.nextMs      = ms + self.cfg.pauseMs
52
+                
53
+            else:
54
+                for i in range(0,self.curNoteCnt+1):
55
+                    if self.cfg.useVelTableFl:
56
+                        self.api.note_on_vel(self.cfg.pitchL[i], 45 )
57
+                    else:
58
+                        self.api.note_on_us(self.cfg.pitchL[i], self.cfg.atkUsec)
59
+                    time.sleep( 0.02 )
60
+                    
61
+                self.nextMs = ms + self.cfg.durMs
62
+                self.isPlayingFl = True
63
+                    
64
+                    
65
+                    
66
+
67
+        
68
+            

+ 5
- 3
MidiDevice.py View File

@@ -136,10 +136,12 @@ class MidiDevice(object):
136 136
         o_msgL = []
137 137
         
138 138
         if self.mip is not None:
139
-            midi_msg = self.mip.get_message()
140
-            if midi_msg and midi_msg[0]:
139
+            while True:
140
+                midi_msg = self.mip.get_message()
141
+                if not midi_msg or not midi_msg[0]:
142
+                    break;
141 143
                 
142
-                if self.monitorInFl:
144
+                if self.inMonitorFl:
143 145
                     o_msgL.append( self._midi_data_to_text_msg(True,midi_msg[0]) )
144 146
                     
145 147
                 if self.throughFl and self.mop is not None:

+ 119
- 0
NoteTester.py View File

@@ -0,0 +1,119 @@
1
+import sys,os,types,json
2
+from random import randrange
3
+
4
+class NoteTester:
5
+    def __init__( self, cfg, api  ):
6
+        self.cfg = cfg
7
+        self.api = api
8
+
9
+        r = types.SimpleNamespace(**cfg.NoteTester)
10
+        
11
+        self.durMsL   = [ randrange(r.minNoteDurMs,  r.maxNoteDurMs)  for _ in range(r.noteCount) ]
12
+        self.pauseMsL = [ randrange(r.minPauseDurMs, r.maxPauseDurMs) for _ in range(r.noteCount) ]
13
+        self.eventL   = []
14
+        self.nextMs   = 0        # next transition time
15
+        self.eventIdx = 0        # next event to play
16
+        self.noteOnFl = False    # True if note is currently sounding
17
+        self.pitch    = r.pitch  #
18
+        self.filename    = r.filename
19
+        self.isStartedFl = False
20
+        self.minAttackUsec = r.minAttackUsec
21
+        self.maxAttackUsec = r.maxAttackUsec
22
+    
23
+    def start( self ):
24
+        self.eventIdx = 0
25
+        self.noteOnFl = False
26
+        self.nextMs   = 0
27
+        self.isStartedFl = True
28
+
29
+    def stop( self ):
30
+        self.isStartedFl = False
31
+        self.write()
32
+
33
+    def tick( self, ms ):
34
+
35
+        if self.isStartedFl and ms > self.nextMs:
36
+
37
+            offsMs = 0
38
+            
39
+            if self.noteOnFl:
40
+                self.noteOnFl = False
41
+                self.api.note_off( self.pitch )                
42
+                offsMs = self.pauseMsL[ self.eventIdx ]
43
+                self.eventIdx += 1
44
+                print("off:%i ms" % (offsMs))
45
+                    
46
+
47
+            else:
48
+                usec        = self.minAttackUsec + (int(self.eventIdx * 250) % int(self.maxAttackUsec - self.minAttackUsec))
49
+                decay_level = self.api.calc_decay_level( usec )
50
+
51
+                self.api.note_on_us( self.pitch, usec, decay_level )
52
+                offsMs = self.durMsL[ self.eventIdx ]
53
+                print("usec:%i %i dcy:%i" % (usec,offsMs, decay_level) )
54
+                self.noteOnFl = True
55
+                
56
+
57
+            self.eventL.append( (ms, self.noteOnFl) )
58
+            self.nextMs = ms + offsMs
59
+    
60
+            if self.eventIdx >= len(self.durMsL):
61
+                self.write();
62
+                self.isStartedFl = False
63
+                print("done % i" % (len(self.eventL)))
64
+
65
+        
66
+        
67
+    def write( self ):
68
+
69
+        with open(self.filename,"w") as f:
70
+            json.dump({ "eventL":self.eventL },f )
71
+
72
+
73
+
74
+def note_tester_compare( nt_fn, logica_fn ):
75
+
76
+    eventL  = []
77
+    logicaL = []
78
+
79
+    with open(nt_fn,"r") as f:
80
+        r = json.load(f)
81
+        eventL =  r['eventL']
82
+        eventL = [ (ms-eventL[0][0], level ) for ms,level in eventL  ]
83
+
84
+    with open(logica_fn,"r") as f:
85
+        logicaL = [ ( d['count']/16e3,d['level'])  for d in json.load(f) if d['signal'] == 0 ]
86
+        logicaL = [ (ms-logicaL[0][0], level!=0 ) for ms,level in logicaL ]
87
+
88
+
89
+    print(len(eventL))
90
+    print(len(logicaL))
91
+
92
+    #edL = [ eventL[i][0]  - eventL[i-1][0]  for i in range(2,len(eventL)) ]
93
+    #ldL = [ logicaL[i][0] - logicaL[i-1][0] for i in range(2,len(logicaL)) ]
94
+
95
+    #print(edL[:10])
96
+    #print(ldL[:10])
97
+
98
+
99
+    durMs = 0
100
+    ms = 0
101
+    for i,(t0,t1) in enumerate(zip(eventL,logicaL)):
102
+        t = t0[0]                              # eventL[] time
103
+        dt = int(t - t1[0])                    # diff between eventL[] and logicaL[] time
104
+        fl = ' ' if t0[1] == t1[1] else '*'    # mark level mismatch with '*'
105
+        print("%5i %7i %4i %i %s" % (i,durMs,dt,t0[1],fl))
106
+        durMs = t-ms                             
107
+        ms = t
108
+        
109
+    
110
+if __name__ == "__main__":
111
+
112
+    nt_fn = "note_tester.json"
113
+    logica_fn = sys.argv[1]
114
+    if len(sys.argv) > 2:
115
+        nt_fn     = sys.argv[2]
116
+
117
+    note_tester_compare( nt_fn, logica_fn)
118
+        
119
+    

+ 148
- 0
PolyNoteTester.py View File

@@ -0,0 +1,148 @@
1
+import types
2
+import time
3
+from random import randrange
4
+
5
+class PolyNoteTester:
6
+    def __init__( self, cfg, api  ):
7
+        self.api = api
8
+
9
+        r = types.SimpleNamespace(**cfg.PolyNoteTester)
10
+        self.cfg = r
11
+
12
+        if r.mode == "simple":
13
+            print("mode:simple")
14
+            self.schedL = self._gen_simple_sched(r)
15
+        else:
16
+            print("mode:poly")
17
+            self.schedL = self._gen_sched(r)
18
+
19
+            
20
+        self.schedL = sorted( self.schedL, key=lambda x: x[0] )    
21
+        self.nextMs   = 0        # next transition time
22
+        self.schedIdx = 0        # next event to play
23
+        self.isStartedFl   = False
24
+
25
+        #self._report()
26
+
27
+    def _report( self ):
28
+        for t,cmd,pitch,atkUs in self.schedL:
29
+            print("%s %6i %3i %5i" % (cmd,t,pitch,atkUs))
30
+
31
+    def _gen_simple_sched( self, r ):
32
+        """ Play each note sequentially from lowest to highest. """
33
+        durMs  = int(r.minNoteDurMs + (r.maxNoteDurMs - r.minNoteDurMs)/2)
34
+        ioMs   = int(r.minInterOnsetMs + (r.maxInterOnsetMs - r.minInterOnsetMs)/2)
35
+        atkUs  = int(r.minAttackUsec + (r.maxAttackUsec - r.minAttackUsec)/2)
36
+        schedL = []
37
+
38
+        t0 = 0
39
+        for pitch in range(r.minPitch,r.maxPitch+1):
40
+            schedL.append((t0,'on',pitch,atkUs))
41
+            schedL.append((t0+durMs,'off',pitch,0))
42
+            t0 += durMs + ioMs
43
+
44
+        return schedL
45
+
46
+        
47
+    def _does_note_overlap( self, beg0Ms, end0Ms, beg1Ms, end1Ms ):
48
+        """ if note 0 is entirely before or after note 1 """
49
+        return not (beg0Ms > end1Ms or end0Ms < beg1Ms)
50
+
51
+    def _do_any_notes_overlap( self, begMs, endMs, begEndL ):
52
+        for beg1,end1 in begEndL:
53
+            if self._does_note_overlap(begMs,endMs,beg1,end1):
54
+                return True
55
+        return False
56
+
57
+    def _get_last_end_time( self, begEndL ):
58
+        end0 = 0
59
+        for beg,end in begEndL:
60
+            if end > end0:
61
+                end0 = end
62
+
63
+        return end0
64
+            
65
+                    
66
+    def _gen_sched( self, r ):
67
+
68
+        pitchL = [ randrange(r.minPitch,r.maxPitch) for _ in range(r.noteCount) ]
69
+        durMsL = [ randrange(r.minNoteDurMs, r.maxNoteDurMs) for _ in range(r.noteCount) ]
70
+        ioMsL  = [ randrange(r.minInterOnsetMs, r.maxInterOnsetMs) for _ in range(r.noteCount) ]
71
+        atkUsL = [ randrange(r.minAttackUsec,   r.maxAttackUsec)   for _ in range(r.noteCount) ]
72
+        schedL = []
73
+        pitchD = {} # pitch: [ (begMs,endMs) ]
74
+
75
+        t0 = 0
76
+        # for each pitch,dur,ioi,atkUs tuple
77
+        for pitch,durMs,interOnsetMs,atkUs in zip(pitchL,durMsL,ioMsL,atkUsL):
78
+
79
+            # calc note begin and end time
80
+            begMs = t0
81
+            endMs = t0 + durMs
82
+
83
+            # if this pitch hasn't yet been added to pitchD
84
+            if pitch not in pitchD:
85
+                pitchD[ pitch ] = [ (begMs,endMs) ]
86
+            else:
87
+
88
+                # if the proposed note overlaps with other notes for this pitch
89
+                if self._do_any_notes_overlap( begMs, endMs, pitchD[pitch] ):
90
+                    # move this pitch past the last note
91
+                    begMs = self._get_last_end_time( pitchD[pitch] ) + interOnsetMs
92
+                    endMs = begMs + durMs
93
+
94
+                # add the new note to pitchD
95
+                pitchD[ pitch ].append( (begMs,endMs) )
96
+
97
+            
98
+            # update the schedule
99
+            schedL.append( (begMs, 'on', pitch, atkUs)) 
100
+            schedL.append( (endMs, 'off', pitch, 0 ))
101
+            
102
+            t0 += interOnsetMs
103
+
104
+        
105
+        return schedL
106
+        
107
+        
108
+    def start( self ):
109
+        self.schedIdx = 0
110
+        self.nextMs   = 0
111
+        self.api.set_hold_duty_all(self.cfg.holdDutyPct)
112
+        self.isStartedFl = True
113
+        
114
+
115
+    def stop( self ):
116
+        self.isStartedFl = False
117
+        self.api.all_notes_off()
118
+        
119
+
120
+    def tick( self, ms ):
121
+
122
+        while self.isStartedFl and ms >= self.nextMs and self.schedIdx < len(self.schedL):
123
+
124
+            t0,cmd,pitch,usec = self.schedL[self.schedIdx]
125
+
126
+            if cmd == 'on':
127
+                if pitch not in self.cfg.skipPitchL:
128
+                    decay_level = self.api.calc_decay_level( usec )
129
+                    self.api.note_on_us( pitch, usec, decay_level )
130
+                    print("on  %i %i %i" % (pitch,usec,decay_level))
131
+                
132
+            elif cmd == 'off':
133
+                if pitch not in self.cfg.skipPitchL:
134
+                    self.api.note_off( pitch )
135
+                    print("off %i" % pitch)
136
+            
137
+            self.schedIdx += 1
138
+            if self.schedIdx < len(self.schedL):
139
+                self.nextMs = ms + (self.schedL[self.schedIdx][0] - t0)
140
+            else:
141
+                self.isStartedFl = False
142
+                print("Done.")
143
+        
144
+        
145
+
146
+
147
+
148
+    

+ 23
- 16
README.md View File

@@ -27,10 +27,13 @@ Capture note 60 and 61 using the full_pulseL[] and holdDutyPctD{} from the p_ac.
27 27
 ## Plot Cheat Sheet
28 28
 
29 29
 
30
-![Plot Seq 1](doc/do_td_plot.png)
30
+---
31 31
 
32 32
 Print a specific pitch and take.
33 33
 
34
+![Plot Seq 1](doc/do_td_plot.png)
35
+
36
+
34 37
 ````
35 38
     python plot_seq.py p_ac.yml ~/temp/p_ac_3_of td_plot 60 2
36 39
     
@@ -39,19 +42,20 @@ Print a specific pitch and take.
39 42
 
40 43
 ---
41 44
 
42
-![Multi Usec dB](doc/us_db.png)
43
-
44 45
 Plot all the takes for a given pitch
45 46
 
47
+![Multi Usec dB](doc/us_db.png)
48
+
46 49
 ````
47 50
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db 84
48 51
 ````
49 52
 
50 53
 ---
51 54
 
55
+Plot a specific set of pitches and takes.
56
+
52 57
 ![Overlapping USec dB](doc/us_db_takes.png)
53 58
 
54
-Plot a specific set of pitches and takes.
55 59
 
56 60
 ````
57 61
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_take 75 0 76 0 77 0 78 0 72 10 73 1 74 1
@@ -59,9 +63,10 @@ python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_take 75 0 76 0 77 0 7
59 63
 
60 64
 ---
61 65
 
66
+Plot the last take from a list of pitches.
67
+
62 68
 ![Overlapping USec dB](doc/us_db_takes_last.png)
63 69
 
64
-Plot the last take from a list of pitches.
65 70
 
66 71
 ````
67 72
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_last 77 78 79 80
@@ -70,9 +75,10 @@ python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_of us_db_pitch_last 77 78 79 80
70 75
 
71 76
 ---
72 77
 
78
+Plot the time domain envelope for a specific set of pitches and takes.
79
+
73 80
 ![Multi Plot 1](doc/multi_plot.png)
74 81
 
75
-Plot the time domain envelope for a specific set of pitches and takes.
76 82
 
77 83
 ````
78 84
 python plot_seq.py p_ac.yml ~/temp/p_ac_3_od td_multi_plot 60 3 60 4 60 5
@@ -81,9 +87,10 @@ plot_seq.py `do_td_multi_plot(inDir,cfg.analysisArgs,[(36,4), (48,2)] )
81 87
 ````
82 88
 ---
83 89
 
90
+Plot the spectrum with harmonic location markers of a specific set of pitches and takes.
91
+
84 92
 ![Spectral Ranges](doc/plot_spectral_ranges.png)
85 93
 
86
-Plot the spectrum with harmonic location markers of a specific set of pitches and takes.
87 94
 
88 95
 ````
89 96
 #                                                                 pitch0 takeId0 pitch1 takeId1
@@ -93,21 +100,21 @@ python plot_seq.py p_ac.yml ~/temp/p_ac_3_od plot_spectral_ranges   60      3
93 100
 
94 101
 ---
95 102
 
96
-![Usec dB Spread](doc/us_db_map.png)
97
-
98 103
 Plot the microsecond variance to achieve a given decibel value.
99 104
 
105
+![Usec dB Spread](doc/us_db_map.png)
106
+
100 107
 ````
101 108
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od us_db_map 84 72
102 109
 ````
103 110
 
104 111
 ---
105 112
 
106
-![Resample](doc/resample_pulse_times.png)
107
-
108 113
 Analyze all takes for given pitch and show the mean us/db curve and
109 114
 places where resampling may be necessary.
110 115
 
116
+![Resample](doc/resample_pulse_times.png)
117
+
111 118
 ````
112 119
 python plot_seq.py p_ac.yml ~/temp/p_ac_3_of resample_pulse_times 84
113 120
 ````
@@ -115,30 +122,30 @@ python plot_seq.py p_ac.yml ~/temp/p_ac_3_of resample_pulse_times 84
115 122
 
116 123
 ---
117 124
 
118
-![Min Max](doc/min_max_db.png)
119
-
120 125
 Plot the min and max decibel values for specified pitches.
121 126
 
127
+![Min Max](doc/min_max_db.png)
128
+
122 129
 ````
123 130
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max 36 48 60 72 84
124 131
 ````
125 132
 
126 133
 ---
127 134
 
128
-![Min Max 2](doc/min_max_db_2.png)
129
-
130 135
 Plot the min and max decibel values for specified pitches.
131 136
 
137
+![Min Max 2](doc/min_max_db_2.png)
138
+
132 139
 ````
133 140
 #                                                        pitch0 pitch1 pitch2 pitch3 pitch4 takeId
134 141
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od min_max_2   36     48      60     72     84     2
135 142
 ````
136 143
 
137 144
 ---
138
-![Manual dB](doc/manual_db.png)
139 145
 
140 146
 Plot the min and max decibel values for specified set of manually corrected pitches.
141 147
 
148
+![Manual dB](doc/manual_db.png)
142 149
 
143 150
 ````
144 151
 python plot_seq_1.py p_ac.yml ~/temp/p_ac_3_od manual_db

+ 148
- 0
VelTablePlayer.py View File

@@ -0,0 +1,148 @@
1
+import json
2
+from rt_note_analysis import RT_Analyzer
3
+
4
+class VelTablePlayer:
5
+    
6
+    def __init__( self, cfg, api, audio, holdDutyPctD, fn  ):
7
+        self.cfg          = cfg
8
+        self.api          = api
9
+        self.audio        = audio
10
+        self.rtAnalyzer   = RT_Analyzer()
11
+        self.holdDutyPctD = holdDutyPctD
12
+        self.durMs        = 500
13
+        self.mode         = "across"
14
+        self.state        = "off"
15
+        self.minPitch     = 21
16
+        self.maxPitch     = 108
17
+        self.velMapD      = {}
18
+
19
+        self.curMaxPitch  = self.maxPitch
20
+        self.curMinPitch  = self.minPitch
21
+        self.curPitch     = 21
22
+        self.curVelocity  = 0
23
+        self.curEndMs     = 0
24
+        self.curBegNoteMs = 0
25
+        self.curEndNoteMs = 0
26
+        
27
+        with open(fn,"r") as f:
28
+            d = json.load(f)
29
+
30
+            for pitch,value in d.items():
31
+                self.velMapD[ int(pitch) ] = [ int(x[0]) for x in d[pitch] ]
32
+
33
+        assert self.minPitch in self.velMapD
34
+        assert self.maxPitch in self.velMapD
35
+
36
+        
37
+    def start( self, minPitch, maxPitch, mode  ):
38
+        self.curMaxPitch = maxPitch
39
+        self.curMinPitch = minPitch
40
+        self.curPitch    = minPitch
41
+        self.curVelocity = 0
42
+        self.state       = "note_on"
43
+        self.mode        = mode 
44
+        self.audio.record_enable(True)   # start recording audio
45
+           
46
+    def stop( self ):
47
+        self.curPitch = self.minPitch
48
+        self._all_notes_off()
49
+        self.audio.record_enable(False)
50
+
51
+    def tick( self, ms ):
52
+        if self.state == "off":
53
+            pass
54
+        
55
+        elif self.state == "note_on":
56
+            self.state = self._note_on(ms)
57
+            
58
+        elif self.state == "playing":
59
+            if ms >= self.curEndMs:
60
+                self.state = "note_off"
61
+        
62
+        elif self.state == "note_off":
63
+            self.state = self._note_off(ms)
64
+
65
+
66
+    def _get_duty_cycle( self, pitch, usec ):
67
+        usDutyL = self.holdDutyPctD[pitch]
68
+
69
+        for i in range(len(usDutyL)):
70
+            if usDutyL[i][0] >= usec:
71
+                return usDutyL[i][1]
72
+
73
+        return usDutyL[-1][1]
74
+
75
+    def _calc_next_pitch( self ):
76
+
77
+        self.curPitch += 1
78
+        while self.curPitch not in self.velMapD and self.curPitch <= self.curMaxPitch:
79
+            self.curPitch+1
80
+
81
+        return self.curPitch <= self.curMaxPitch
82
+    
83
+    def _get_next_note_params( self ):
84
+
85
+        usec    = None
86
+        dutyPct = None
87
+        doneFl  = False
88
+
89
+        if self.mode == "updown":
90
+            if self.curVelocity + 1 < len(self.velMapD[ self.curPitch ]):
91
+                self.curVelocity += 1
92
+            else:
93
+
94
+                if self._calc_next_pitch():
95
+                    self.curVelocity = 0
96
+                else:
97
+                    doneFl = True
98
+
99
+        else:
100
+            
101
+            if self._calc_next_pitch():
102
+                self.curPitch += 1
103
+            else:
104
+                if self.curVelocity + 1 < len(self.velMapD[ self.curPitch ]):
105
+                    self.curVelocity += 1
106
+                    self.curPitch = self.curMinPitch
107
+                else:
108
+                    doneFl = True
109
+
110
+        if doneFl:
111
+            self.audio.record_enable(False)
112
+        else:
113
+            usec    = self.velMapD[self.curPitch][self.curVelocity]
114
+        
115
+            dutyPct = self._get_duty_cycle( self.curPitch, usec )
116
+            
117
+
118
+        return self.curPitch, usec, dutyPct
119
+            
120
+    def _note_on( self, ms ):
121
+
122
+        pitch,usec,dutyPct = self._get_next_note_params()
123
+
124
+        if not usec:
125
+            return "off"
126
+        else:
127
+            print(self.curPitch,self.curVelocity,usec,dutyPct)
128
+            self.curBegNoteMs = self.audio.buffer_sample_ms().value
129
+            self.api.set_pwm_duty( pitch, dutyPct )
130
+            self.api.note_on_us( pitch, usec )
131
+            self.curEndMs = ms + self.durMs
132
+            return "playing"
133
+        
134
+    def _note_off( self, ms ):
135
+
136
+        self.curEndNoteMs = self.audio.buffer_sample_ms().value
137
+        self.rtAnalyzer.analyze_note( self.audio, self.curPitch, self.curBegNoteMs, self.curEndNoteMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
138
+        self.api.note_off( self.curPitch )
139
+        return "note_on"
140
+
141
+
142
+    def _all_notes_off( self ):
143
+        if self.curPitch == 109:
144
+            self.state = 'off'
145
+            print('done')
146
+        else:
147
+            self.api.note_off( self.curPitch )
148
+            self.curPitch += 1

+ 2
- 2
elbow.py View File

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

+ 138
- 33
p_ac.py View File

@@ -19,6 +19,10 @@ from keyboard         import Keyboard
19 19
 from calibrate        import Calibrate
20 20
 from rms_analysis import rms_analyze_one_rt_note_wrap
21 21
 from MidiFilePlayer import MidiFilePlayer
22
+from VelTablePlayer import VelTablePlayer
23
+from NoteTester     import NoteTester
24
+from PolyNoteTester import PolyNoteTester
25
+from ChordTester    import ChordTester
22 26
 
23 27
 class AttackPulseSeq:
24 28
     """ Sequence a fixed pitch over a list of attack pulse lengths."""
@@ -33,7 +37,7 @@ class AttackPulseSeq:
33 37
         self.noteDurMs   = noteDurMs      # duration of each chord in milliseconds
34 38
         self.pauseDurMs  = pauseDurMs     # duration between end of previous note and start of next
35 39
         self.holdDutyPctL= None           # hold voltage duty cycle table [ (minPulseSeqUsec,dutyCyclePct) ]
36
-        self.holdDutyPctD= None          # { us:dutyPct } for each us in self.pulseUsL
40
+        self.holdDutyPctD= None           # { us:dutyPct } for each us in self.pulseUsL
37 41
         self.silentNoteN = None 
38 42
         self.pulse_idx            = 0     # Index of next pulse 
39 43
         self.state                = None  # 'note_on','note_off'
@@ -45,25 +49,28 @@ class AttackPulseSeq:
45 49
         self.rtAnalyzer           = RT_Analyzer()
46 50
 
47 51
     def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, holdDutyPctD, playOnlyFl=False ):
48
-        self.outDir     = outDir         # directory to write audio file and results
49
-        self.pitch     = pitch           # note to play
50
-        self.pulseUsL   = pulseUsL       # one onset pulse length in microseconds per sequence element
51
-        self.holdDutyPctL = holdDutyPctL
52
-        self.holdDutyPctD = holdDutyPctD
53
-        self.silentNoteN = 0 
54
-        self.pulse_idx  = 0
55
-        self.state      = 'note_on'
52
+        self.outDir          = outDir         # directory to write audio file and results
53
+        self.pitch           = pitch          # note to play
54
+        self.pulseUsL        = pulseUsL       # one onset pulse length in microseconds per sequence element
55
+        self.holdDutyPctL    = holdDutyPctL
56
+        self.holdDutyPctD    = holdDutyPctD
57
+        self.silentNoteN     = 0 
58
+        self.pulse_idx       = 0
59
+        self.state           = 'note_on'
56 60
         self.prevHoldDutyPct = None
57
-        self.next_ms    = ms + 500       # wait for 500ms to play the first note (this will guarantee that there is some empty space in the audio file before the first note)
58
-        self.eventTimeL = [[0,0]  for _ in range(len(pulseUsL))] # initialize the event time         
59
-        self.beginMs    = ms
60
-        self.playOnlyFl = playOnlyFl
61
+        self.next_ms         = ms + 500       # wait for 500ms to play the first note (this will guarantee that there is some empty space in the audio file before the first note)
62
+        self.eventTimeL      = [[0,0]  for _ in range(len(pulseUsL))] # initialize the event time         
63
+        self.beginMs         = ms
64
+        self.playOnlyFl      = playOnlyFl
61 65
                     
62 66
         # kpl if not playOnlyFl:
63 67
         self.audio.record_enable(True)   # start recording audio
64 68
 
65
-        print("Hold Delay from end of attack:",cfg.defaultHoldDelayUsecs)                    
66
-        self.api.set_hold_delay(pitch,cfg.defaultHoldDelayUsecs)
69
+        #print("Hold Delay from end of attack:",cfg.defaultHoldDelayUsecs)                    
70
+        # self.api.set_hold_delay(pitch,cfg.defaultHoldDelayUsecs)
71
+
72
+        self.api.set_hold_duty(pitch,35)
73
+        print("Hold Duty Cycle=35.")
67 74
         
68 75
         self.tick(ms)                    # play the first note
69 76
         
@@ -128,15 +135,36 @@ class AttackPulseSeq:
128 135
             
129 136
     def _set_duty_cycle( self, pitch, pulseUsec ):
130 137
 
131
-        
132
-        dutyPct = self._get_duty_cycle( pulseUsec )
138
+        if False:
139
+            dutyPct = self._get_duty_cycle( pulseUsec )
133 140
 
134
-        if dutyPct != self.prevHoldDutyPct:
135
-            self.api.set_pwm_duty( pitch, dutyPct )
136
-            print("Hold Duty:",dutyPct)
137
-            
138
-        self.prevHoldDutyPct = dutyPct
139
-    
141
+            if dutyPct != self.prevHoldDutyPct:
142
+                # self.api.set_pwm_duty( pitch, dutyPct )
143
+                # print("Hold Duty:",dutyPct)
144
+                pass
145
+
146
+            self.prevHoldDutyPct = dutyPct
147
+
148
+        if False:
149
+            maxLevel   =  64 # 225  # 64
150
+            minLevel   =  24 #  89 #  24
151
+            maxUsec    = 10000
152
+            minUsec    =  2500
153
+            decayLevel = maxLevel
154
+
155
+            if pulseUsec < maxUsec and pulseUsec >= minUsec:
156
+                decayLevel = minLevel + ((pulseUsec-minUsec)/(maxUsec-minUsec)) * (maxLevel-minLevel)
157
+            else:
158
+                if pulseUsec < minUsec:
159
+                    decayLevel = minLevel
160
+
161
+            decayLevel = int(decayLevel)
162
+
163
+            decayLevel = self.api.calc_decay_level( pulseUsec )
164
+            self.api.set_decay_level( pitch, decayLevel )
165
+            print("Decay Level:", decayLevel)
166
+
167
+        
140 168
     def _note_on( self, ms ):
141 169
 
142 170
         self.eventTimeL[ self.pulse_idx ][0] = self.audio.buffer_sample_ms().value
@@ -144,9 +172,13 @@ class AttackPulseSeq:
144 172
         self.state = 'note_off'
145 173
 
146 174
         pulse_usec = int(self.pulseUsL[ self.pulse_idx ])
147
-        self._set_duty_cycle( self.pitch, pulse_usec )
175
+        
176
+        # decay_level = self.api.calc_decay_level( pulse_usec )
177
+
178
+        #self._set_duty_cycle( self.pitch, pulse_usec )
179
+        
148 180
         self.api.note_on_us( self.pitch, pulse_usec )
149
-        print("note-on:",self.pitch, self.pulse_idx, pulse_usec)
181
+        print("note-on:",self.pitch, self.pulse_idx, pulse_usec, "dcy:", decay_level)
150 182
 
151 183
     def _note_off( self, ms ):
152 184
         self.eventTimeL[ self.pulse_idx ][1] = self.audio.buffer_sample_ms().value
@@ -209,7 +241,7 @@ class CalibrateKeys:
209 241
             self.pulseUsL  = pulseUsL
210 242
             self.pitchL   = pitchL
211 243
             self.pitch_idx = -1
212
-            self._start_next_note( ms, playOnlyFl )
244
+            self._start_next_seq( ms, playOnlyFl )
213 245
         
214 246
         
215 247
     def stop( self, ms ):
@@ -225,11 +257,11 @@ class CalibrateKeys:
225 257
 
226 258
             # if the sequencer is done playing 
227 259
             if not self.seq.is_enabled():
228
-                self._start_next_note( ms, self.seq.playOnlyFl ) # ... else start the next sequence
260
+                self._start_next_seq( ms, self.seq.playOnlyFl ) # ... else start the next sequence
229 261
 
230 262
         return None
231 263
 
232
-    def _start_next_note( self, ms, playOnlyFl ):
264
+    def _start_next_seq( self, ms, playOnlyFl ):
233 265
         
234 266
         self.pitch_idx += 1
235 267
 
@@ -253,8 +285,6 @@ class CalibrateKeys:
253 285
             # get the next available output directory id
254 286
             outDir_id = self._calc_next_out_dir_id( outDir )
255 287
 
256
-            print(outDir_id,outDir)
257
-            
258 288
             # if this is not the first time this note has been sampled then get the resample locations
259 289
             if (outDir_id == 0) or self.cfg.useFullPulseListFl:
260 290
                 self.pulseUsL = self.cfg.full_pulseL
@@ -265,9 +295,10 @@ class CalibrateKeys:
265 295
 
266 296
             holdDutyPctL = self.cfg.calibrateArgs['holdDutyPctD'][pitch]
267 297
             
268
-            
298
+            # if playback of the current calibration was requested
269 299
             if playOnlyFl:
270 300
 
301
+                # form the calibrated pulse list 
271 302
                 self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( baseDir,  pitch,  self.cfg.analysisArgs, take_id=None )
272 303
 
273 304
                 noteN = cfg.analysisArgs['auditionNoteN']
@@ -318,6 +349,10 @@ class App:
318 349
         self.keyboard       = None
319 350
         self.calibrate      = None
320 351
         self.midiFilePlayer = None
352
+        self.velTablePlayer = None
353
+        self.noteTester     = None
354
+        self.polyNoteTester = None
355
+        self.chordTester    = None
321 356
         
322 357
     def setup( self, cfg ):
323 358
         self.cfg = cfg
@@ -369,6 +404,15 @@ class App:
369 404
 
370 405
                 self.midiFilePlayer = MidiFilePlayer( cfg, self.api, self.midiDev, cfg.midiFileFn )
371 406
 
407
+                self.velTablePlayer = VelTablePlayer( cfg, self.api, self.audioDev, self.cfg.calibrateArgs['holdDutyPctD'], "velMapD.json")
408
+
409
+                self.noteTester = NoteTester(cfg,self.api)
410
+
411
+                self.polyNoteTester = PolyNoteTester(cfg,self.api)
412
+
413
+                self.chordTester    = ChordTester(cfg,self.api)
414
+
415
+
372 416
         return res
373 417
 
374 418
     def tick( self, ms ):
@@ -388,13 +432,30 @@ class App:
388 432
         if self.midiFilePlayer:
389 433
             self.midiFilePlayer.tick(ms)
390 434
 
435
+        if self.midiDev:
436
+            msgL = self.midiDev.get_input()
437
+            for msg in msgL:
438
+                print(msg['value']);
439
+
440
+        if self.velTablePlayer:
441
+            self.velTablePlayer.tick(ms)
442
+
443
+        if self.noteTester:
444
+            self.noteTester.tick(ms)
445
+
446
+        if self.polyNoteTester:
447
+            self.polyNoteTester.tick(ms)
448
+                
449
+        if self.chordTester:
450
+            self.chordTester.tick(ms)
451
+            
391 452
     def audio_dev_list( self, ms ):
392 453
 
393 454
         if self.audioDev is not None:
394 455
             portL = self.audioDev.get_port_list( True )
395 456
         
396 457
             for port in portL:
397
-                print("chs:%4i label:%s" % (port['chN'],port['label']))
458
+                print("chs:%4i label:'%s'" % (port['chN'],port['label']))
398 459
         
399 460
     def midi_dev_list( self, ms ):
400 461
         d = self.midiDev.get_port_list( True )
@@ -447,6 +508,33 @@ class App:
447 508
     def midi_file_player_stop( self, ms ):
448 509
         self.midiFilePlayer.stop(ms)
449 510
 
511
+    def vel_table_updown( self, ms, argL):
512
+        self.velTablePlayer.start(argL[0],argL[1],"updown")
513
+
514
+    def vel_table_across( self, ms, argL ):
515
+        self.velTablePlayer.start(argL[0],argL[1],"across")
516
+
517
+    def vel_table_stop( self, ms ):
518
+        self.velTablePlayer.stop()
519
+
520
+    def note_tester_start( self, ms ):
521
+        self.noteTester.start();
522
+
523
+    def note_tester_stop( self, ms ):
524
+        self.noteTester.stop();
525
+
526
+    def poly_note_tester_start( self, ms ):
527
+        self.polyNoteTester.start();
528
+
529
+    def poly_note_tester_stop( self, ms ):
530
+        self.polyNoteTester.stop();
531
+
532
+    def chord_tester_start( self, ms ):
533
+        self.chordTester.start()
534
+
535
+    def chord_tester_stop( self, ms ):
536
+        self.chordTester.stop()
537
+        
450 538
     def pedal_down( self, ms ):
451 539
         print("pedal_down")
452 540
         self.midiDev.send_controller(64, 100 )
@@ -454,6 +542,12 @@ class App:
454 542
     def pedal_up( self, ms ):
455 543
         print("pedal_up");
456 544
         self.midiDev.send_controller(64, 0 )
545
+
546
+    def all_notes_off(self, ms):
547
+        self.api.all_notes_off();
548
+
549
+    def check_for_errors(self,ms):
550
+        self.api.check_for_serial_errors()
457 551
         
458 552
     def quit( self, ms ):
459 553
         if self.api:
@@ -587,8 +681,19 @@ class Shell:
587 681
             'R':{ "func":"keyboard_repeat_target_db", "minN":1,  "maxN":1, "help":"Repeat db across keyboard with new pulse_idx"},
588 682
             'F':{ "func":"midi_file_player_start",    "minN":0,  "maxN":0, "help":"Play the MIDI file."},
589 683
             'f':{ "func":"midi_file_player_stop",     "minN":0,  "maxN":0, "help":"Stop the MIDI file."},
684
+            'V':{ "func":"vel_table_updown",          "minN":2,  "maxN":2, "help":"Play Velocity Table up/down - across."},
685
+            'A':{ "func":"vel_table_across",          "minN":2,  "maxN":2, "help":"Play Velocity Table across - up/down."},
686
+            'v':{ "func":"vel_table_stop",            "minN":0,  "maxN":0, "help":"Stop the velocity table playback."},
687
+            'N':{ "func":"note_tester_start",         "minN":0,  "maxN":0, "help":"Play a note using NoteTester."},
688
+            'n':{ "func":"note_tester_stop",          "minN":0,  "maxN":0, "help":"Stop NoteTester."},
689
+            'T':{ "func":"poly_note_tester_start",    "minN":0,  "maxN":0, "help":"Play random notes using PolyNoteTester."},
690
+            't':{ "func":"poly_note_tester_stop",     "minN":0,  "maxN":0, "help":"Stop NoteTester."},
691
+            'H':{ "func":"chord_tester_start",        "minN":0,  "maxN":0, "help":"Chord tester start."},
692
+            'h':{ "func":"chord_tester_stop",         "minN":0,  "maxN":0, "help":"Chord tester stop."},
590 693
             'P':{ "func":"pedal_down",                "minN":0,  "maxN":0, "help":"Pedal down."},
591
-            'U':{ "func":"pedal_up",                  "minN":0,  "maxN":0, "help":"Pedal up."},
694
+            'p':{ "func":"pedal_up",                  "minN":0,  "maxN":0, "help":"Pedal up."},
695
+            'O':{ "func":"all_notes_off",             "minN":0,  "maxN":0, "help":"All notes off."},
696
+            'E':{ "func":"check_for_errors",          "minN":0,  "maxN":0, "help":"Check for errors."}
592 697
             }
593 698
 
594 699
     def _help( self, _=None ):

+ 261
- 98
p_ac.yml View File

@@ -5,6 +5,7 @@
5 5
     # Audio device setup
6 6
     audio: {
7 7
       inPortLabel: "8 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
8
+      #inPortLabel: "8 Scarlett 18i20 USB: Audio",
8 9
       outPortLabel: ,
9 10
     },
10 11
 
@@ -12,10 +13,60 @@
12 13
         inMonitorFl: False,
13 14
         outMonitorFl: False,
14 15
         throughFl: False,
15
-        inPortLabel: "Fastlane:Fastlane MIDI A",
16
-        outPortLabel: "Fastlane:Fastlane MIDI A"
16
+        #inPortLabel: "MIDI9/QRS PNOScan:MIDI9/QRS PNOScan MIDI 1",
17
+        #outPortLabel: "MIDI9/QRS PNOScan:MIDI9/QRS PNOScan MIDI 1"
18
+        #inPortLabel: "Fastlane:Fastlane MIDI A",
19
+        #outPortLabel: "Fastlane:Fastlane MIDI A",
17 20
         #inPortLabel: "picadae:picadae MIDI 1",
18
-        #outPortLabel: "picadae:picadae MIDI 1"
21
+        #outPortLabel: "picadae:picadae MIDI 1"        
22
+    },
23
+
24
+    NoteTester:
25
+    {
26
+      noteCount: 200,
27
+      minNoteDurMs: 100,
28
+      maxNoteDurMs: 1000,
29
+      minPauseDurMs: 5,
30
+      maxPauseDurMs: 500,
31
+      pitch: 60,
32
+      minAttackUsec: 4000,
33
+      maxAttackUsec: 20000,
34
+      filename: "note_tester.json"
35
+    },
36
+
37
+    PolyNoteTester:
38
+    {
39
+      mode: "simple",
40
+      noteCount: 11,
41
+      minPitch: 21,
42
+      maxPitch: 31,
43
+      skipPitchL: [22,68,102],
44
+      minNoteDurMs: 1000,
45
+      maxNoteDurMs: 2000,
46
+      minInterOnsetMs: 100,
47
+      maxInterOnsetMs: 1000,
48
+      minAttackUsec: 4000,
49
+      maxAttackUsec: 20000,
50
+      holdDutyPct: 35,
51
+      
52
+    },
53
+
54
+    ChordTester:
55
+    {
56
+       useVelTableFl: False,
57
+       #pitchL: [24,36,48,60,72,84,96],
58
+       #pitchL: [24,96,48,72,36,84,60],
59
+       #pitchL: [ 36,40,43,47, 48, 52, 55, 59],
60
+       #pitchL:  [ 36,39,41,33 ],
61
+       pitchL:   [ 21,23,24,25,26,27,28,29,30,31],
62
+       #pitchL:  [ 36,33,35,34,37,40,43,42,38,39,41],
63
+       #pitchL:   [ 43,44,45,46,47,48,49,50,51,52,53],
64
+       #pitchL:  [ 54,55,56,57,58,59,60,61,62,63,64 ],
65
+       repeatCnt: 3,
66
+       atkUsec: 10000,
67
+       durMs: 1000,
68
+       pauseMs: 1000,
69
+       holdDuty: 35,
19 70
     },
20 71
     
21 72
     # Picadae API args
@@ -28,11 +79,11 @@
28 79
     
29 80
 
30 81
     # MeasureSeq args
31
-    outDir: "~/temp/p_ac_3_of",
82
+    outDir: "~/temp/p_ac_3_ok",
32 83
     noteDurMs: 500,
33 84
     pauseDurMs: 500,
34 85
     reversePulseListFl: True,
35
-    useFullPulseListFl: True,
86
+    useFullPulseListFl: True,     # don't select a new set of resample points based on the previous samples from this pitch
36 87
     maxSilentNoteCount: 4,
37 88
     silentNoteMaxPulseUs: 15000,
38 89
     silentNoteMinDurMs: 180, #250,
@@ -40,7 +91,7 @@
40 91
     defaultHoldDelayUsecs: 1000,
41 92
 
42 93
     # Midi file player
43
-    midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt",
94
+    midiFileFn: "/home/kevin/media/audio/midi/txt/988-v25.txt",
44 95
     
45 96
     
46 97
 
@@ -94,7 +145,26 @@
94 145
     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 146
 
96 147
     # 60,72,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, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000  ],
148
+    full_pulse23L: [  2000, 2250, 2500,2750, 3000, 3250, 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000  ],
149
+
150
+    full_pulseL24: [  5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6500, 6750, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000  ],
151
+
152
+    # 23-40
153
+    full_pulseL25: [  3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000  ],
154
+
155
+    full_pulse25aL: [  2000, 2050, 2100, 2150, 2200, 2250, 2300, 2350, 2400, 2450, 2500, 2550, 2600, 2650, 2700, 2750, 2800, 2850, 2900, 2950, 3000, 3050, 3100, 3150, 3200, 3250, 3300, 3350, 3400, 3450, 3500, 3550, 3600, 3650, 3700, 3750, 3800, 3850, 3900, 3950, 4000, 4050, 4100, 4150, 4200, 4250, 4300, 4350, 4400, 4450, 4500, 4550, 4600, 4650, 4700, 4750, 4800, 4850, 4900, 4950, 5000, 5050, 5100, 5150, 5200, 5250, 5300, 5350, 5400, 5450, 5500, 5550, 5600, 5650, 5700, 5750, 5800, 5850, 5900, 5950, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000  ],
156
+
157
+    # 21-    
158
+    full_pulse26L: [  1500, 1625, 1750, 1875, 2000, 2125, 2250, 2375, 2500, 2625, 2750, 2875, 3000, 3125, 3250, 3375,3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000  ],
159
+
160
+    full_pulseL: [  1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 22000  ],
161
+    
162
+    # 92
163
+    full_pulse27L: [  1500, 1625, 1750, 1875, 2000, 2125, 2250, 2375, 2500, 2625, 2750, 2875, 3000, 3125, 3250, 3375, 3500, 3625, 3750, 3875, 4000, 4125, 4250, 4375, 4500, 4625, 4750, 4875, 5000, 5125, 5250, 5375, 5500, 5625, 5750, 5875, 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 14000, 15000, 16000, 17000, 18000, 19000  ],
164
+
165
+    full_pulse28L: [ 4000, 8000, 12000, 16000 ],
166
+
167
+    full_pulse29L: [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000, 12500, 13000, 13500, 14000, 14500, 15000, 15500, 16000, 16500, 17000, 17500, 18000, 18500, 19000, 19500, 20000 ],
98 168
 
99 169
     # RMS analysis args
100 170
     analysisArgs: {
@@ -112,28 +182,113 @@
112 182
       resampleMinDurMs: 150,         # notes's whose duration is less than this will be skipped
113 183
 
114 184
       useLastTakeOnlyFl: True,
115
-      minAttkDb: -5.0,   # threshold of silence level 
116
-      maxDbOffset: 0.25, # travel down the from the max. note level by at most this amount to locate the max. peak
117
-      maxDeltaDb: 1.5,  # maximum db change between volume samples (changes greater than this will trigger resampling)
118
-      samplesPerDb: 4,   # count of samples per dB to resample ranges whose range is less than maxDeltaDb
119
-      minSampleDistUs: 50, # minimum distance between sample points in microseconds
185
+      minAttkDb: -5.0,      # threshold of silence level 
186
+      maxDbOffset: 0.25,    # travel down the from the max. note level by at most this amount to locate the max. peak
187
+      maxDeltaDb: 1.5,      # maximum db change between volume samples (changes greater than this will trigger resampling)
188
+      samplesPerDb: 4,      # count of samples per dB to resample ranges whose range is less than maxDeltaDb
189
+      minSampleDistUs: 50,  # minimum distance between sample points in microseconds
120 190
       auditionNoteN: 19,    # count of notes to play for audition
121 191
 
122 192
       finalPulseListCacheFn: "/home/kevin/temp/final_pulse_list_cache.pickle",
123 193
       rmsAnalysisCacheFn: "/home/kevin/temp/rms_analysis_cache.pickle"
124 194
       },
125 195
 
196
+      # used by plot_seq_1.gen_vel_map()
197
+      velTableDbL: [ 2, 7, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29  ],
198
+
126 199
       manualMinD: {
127
-        36: [2, 10],
128
-        48: [2, 10],
129
-        60: [2, 10],
130
-        72: [2, 10],
131
-        84: [2, 10]
132
-        },
200
+      21:[-1,6], 
201
+      23:[-1,1], # 10
202
+      24:[-1,11], # 11 
203
+      25:[-1,8], 
204
+      26:[-1,9], # 13 
205
+      27:[-1,11], 
206
+      28:[-1,2], 
207
+      29:[-1,16], 
208
+      30:[-1,4], 
209
+      31:[-1,8], 
210
+      32:[-1,9], 
211
+      33:[-1,10], 
212
+      34:[-1,9], # 15 
213
+      35:[-1,9], 
214
+      36:[-1,4], 
215
+      37:[-1,7], 
216
+      38:[-1,5], 
217
+      39:[-1,5], 
218
+      40:[-1,6],  # 2  lwr
219
+      41:[-1,10],  # 24 lwr
220
+      42:[-1,2], 
221
+      43:[-1,4], 
222
+      44:[-1,5], 
223
+      45:[-1,10], 
224
+      46:[-1,4], 
225
+      47:[-1,7], 
226
+      48:[-1,9], 
227
+      49:[-1,2],
228
+      50:[-1,5],
229
+      51:[-1,4], 
230
+      52:[-1,8], 
231
+      53:[-1,0], # 16
232
+      54:[-1,8], 
233
+      55:[-1,14], 
234
+      56:[-1,8], 
235
+      57:[-1,9], 
236
+      58:[-1,10], 
237
+      59:[-1,7], 
238
+      60:[-1,7], # 14
239
+      61:[-1,10], # 14
240
+      62:[-1,1], # 7
241
+      63:[-1,5], 
242
+      64:[-1,2], 
243
+      65:[-1,8], 
244
+      66:[-1,3], 
245
+      67:[-1,3], # 17 lwr
246
+      68:[-1,3], 
247
+      69:[-1,9], 
248
+      70:[-1,15], # 15 lwr 
249
+      71:[-1,0], # 7 lwr
250
+      72:[-1,3],# 12 lwr
251
+      73:[-1,0],# 21 lwr
252
+      74:[-1,0],# 24 lwr
253
+      75:[-1,0],# 18 lwr
254
+      76:[-1,0],# 13 lwr
255
+      77:[-1,0],# 22 lwr
256
+      78:[-1,0],# 14 lwr
257
+      79:[-1,8], 
258
+      80:[-1,3], 
259
+      81:[-1,2],  # 14 lwr 
260
+      82:[-1,5],  # 11
261
+      83:[-1,7], 
262
+      84:[-1,3], # 13
263
+      85:[-1,5], 
264
+      86:[-1,4], 
265
+      87:[-1,7], 
266
+      88:[-1,5], # 22
267
+      89:[-1,19], 
268
+      90:[-1,2], # 10
269
+      91:[-1,10], 
270
+      92:[-1,8], 
271
+      93:[-1,7], 
272
+      94:[-1,5], # 10
273
+      95:[-1,9], 
274
+      96:[-1,13], 
275
+      97:[-1,12], 
276
+      98:[-1,14], 
277
+      99:[-1,14], 
278
+      100:[-1,18], 
279
+      101:[-1,18], 
280
+      103:[-1,25], 
281
+      104:[-1,26], 
282
+      105:[-1,18], 
283
+      106:[-1,26], 
284
+      107:[-1,29], 
285
+      108:[-1,28], 
286
+      },
133 287
 
134
-      manualAnchorPitchMinDbL: [ 36,48,72,84 ],
135
-      manualAnchorPitchMaxDbL: [ 36,48,60,72,84 ],
136
-        
288
+      manualAnchorPitchMinDbL: [ 24, 36,48,60,72,84,96 ],
289
+      manualAnchorPitchMaxDbL: [ 24, 36,48,60,72,84,96 ],
290
+
291
+      manualLastFl: true,
137 292
         
138 293
       manualMinD_0: {
139 294
         23: [2, 24],
@@ -256,85 +411,93 @@
256 411
           dbSrcLabel: 'hm',              # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
257 412
 
258 413
           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, 40]],
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, 45]],
296
-          60: [[0, 40],[10000,50]],
297
-          61: [[0, 40],[10000,50]],
298
-          62: [[0, 40],[10000,50]],
299
-          63: [[0, 40],[10000,50]],
300
-          64: [[0, 40],[10000,50]],
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, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
309
-          73: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
310
-          74: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
311
-          75: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
312
-          76: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
313
-          77: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
314
-          78: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
315
-          79: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
316
-          80: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
317
-          81: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
318
-          82: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
319
-          83: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
320
-          84: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
321
-          85: [[0, 42],[8000,44],[9000,46],[10000, 48],[11000,50],[12000,52],[13000,54],[14000,56],[15000,58],[16000,60],[17000,60],[18000,60],[19000,60],[20000,60] ],
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]],
414
+          21: [[0, 50]],
415
+          23: [[0, 50]],
416
+          24: [[0, 50]],
417
+          25: [[0, 50]],
418
+          26: [[0, 50]],
419
+          27: [[0, 50]],
420
+          28: [[0, 50]],
421
+          29: [[0, 50]],
422
+          30: [[0, 50]],
423
+          31: [[0, 50]],
424
+          32: [[0, 50]],
425
+          33: [[0, 50]],
426
+          34: [[0, 50]],
427
+          35: [[0, 50]],
428
+          36: [[0, 50]],
429
+          37: [[0, 50]],
430
+          38: [[0, 50]],
431
+          39: [[0, 50]],
432
+          40: [[0, 50]],
433
+          41: [[0, 45]],
434
+          42: [[0, 45]],
435
+          43: [[0, 45]],
436
+          44: [[0, 45]],
437
+          45: [[0, 45]],
438
+          46: [[0, 40],[10000,45]],
439
+          47: [[0, 40],[10000,45]],
440
+          48: [[0, 40],[10000,45]],
441
+          49: [[0, 40],[10000,45]],
442
+          50: [[0, 40],[10000,45]],
443
+          51: [[0, 40],[10000,45]],
444
+          52: [[0, 40],[10000,45]],
445
+          53: [[0, 40],[10000,45]],
446
+          54: [[0, 40],[10000,45]],
447
+          55: [[0, 40],[10000,45]],
448
+          56: [[0, 40],[10000,45]],
449
+          57: [[0, 40],[10000,45]],
450
+          58: [[0, 40],[10000,45]],
451
+          59: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],       
452
+          60: [[0, 40],[10000,43],[11000,45],[12000,45], [13000,50],[14000,55],[15000,60],[16000,65]],
453
+          61: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
454
+          62: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
455
+          63: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
456
+          64: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
457
+          65: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],              
458
+          66: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
459
+          67: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
460
+          68: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
461
+          69: [[0, 32],[5000,64] ],
462
+          70: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
463
+          71: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
464
+          72: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
465
+          73: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
466
+          74: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
467
+          75: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
468
+          76: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
469
+          77: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55],[15000,60]],
470
+          78: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
471
+          79: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
472
+          80: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
473
+          81: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
474
+          82: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
475
+          83: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
476
+          84: [[0, 35],[8000,40],[10000,45],[12000,48],[13000,50],[14000,55]],
477
+          85: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
478
+          86: [[0, 40],[10000,45],[12000,48],[13000,50],[14000,55]],
479
+          87: [[0, 40],[10000,45],[12000,48],[13000,50]],
480
+          88: [[0, 40],[10000,45],[12000,48],[13000,50]],
481
+          89: [[0, 40],[10000,45],[12000,48],[13000,50]],
482
+          90: [[0, 40],[10000,45],[12000,48],[13000,50]],
483
+          91: [[0, 40],[10000,45],[12000,48],[13000,50]],
484
+          92: [[0, 37]],
485
+          93: [[0, 37]],
486
+          94: [[0, 37]],
487
+          95: [[0, 37]],
488
+          96: [[0, 37]],
332 489
           97: [[0, 40]],
333 490
           98: [[0, 40]],
334
-          99: [[0, 40]],
335
-          100: [[0, 40]],
336
-          101: [[0, 40]],
337
-          106: [[0, 40]]
491
+          99: [[0, 37]],
492
+          100: [[0, 37]],
493
+          101: [[0, 37]],
494
+          102: [[0, 37]],
495
+          103: [[0, 40],[10000,45],[15000,50]],
496
+          104: [[0, 40],[7500,45],[15000,50]],
497
+          105: [[0, 40],[10000,45],[15000,50]],
498
+          106: [[0, 37]],
499
+          107: [[0, 37]],
500
+          108: [[0, 37]],
338 501
           },
339 502
 
340 503
           # Final for Matt's piano

+ 1
- 1
plot_all_note_durations.ipynb View File

@@ -71,7 +71,7 @@
71 71
    "name": "python",
72 72
    "nbconvert_exporter": "python",
73 73
    "pygments_lexer": "ipython3",
74
-   "version": "3.7.5"
74
+   "version": "3.8.6"
75 75
   }
76 76
  },
77 77
  "nbformat": 4,

+ 1
- 1
plot_calibrate.ipynb View File

@@ -67,7 +67,7 @@
67 67
    "name": "python",
68 68
    "nbconvert_exporter": "python",
69 69
    "pygments_lexer": "ipython3",
70
-   "version": "3.7.5"
70
+   "version": "3.8.6"
71 71
   }
72 72
  },
73 73
  "nbformat": 4,

+ 144
- 46
plot_seq_1.py View File

@@ -1,6 +1,6 @@
1 1
 ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org> 
2 2
 ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
3
-import os, sys,json
3
+import os, sys,json,math
4 4
 import matplotlib.pyplot as plt
5 5
 import numpy as np
6 6
 from common import parse_yaml_cfg
@@ -458,17 +458,43 @@ def _plot_us_db_takes( inDir, cfg, pitchL, takeIdL, printDir="", printFn="" ):
458 458
 
459 459
         usL, dbL, durMsL, _, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'], takeId=takeId )
460 460
 
461
+
461 462
         ax.plot(usL,dbL, marker='.',label="%i:%i %s %s" % (midi_pitch,takeId,keyMapD[midi_pitch]['class'],keyMapD[midi_pitch]['type']))
462 463
 
463
-        # for i,(x,y) in enumerate(zip(usL,dbL)):
464
-        #    ax.text(x,y,str(i))
464
+        for i,(x,y) in enumerate(zip(usL,dbL)):
465
+            ax.text(x,y,str(i))
465 466
 
467
+        f_usL,f_dbL = filter_us_db(usL,dbL)
468
+            
469
+        ax.plot(f_usL,f_dbL, marker='.')
470
+            
471
+
472
+        elbow_us,elbow_db = elbow.find_elbow(usL,dbL)
473
+        ax.plot([elbow_us],[elbow_db],marker='*',markersize=12,color='red',linestyle='None')
474
+
475
+        elb_idx = nearest_sample_point( dbL, usL, elbow_db, elbow_us )
466 476
 
467 477
     if printDir:
468 478
         plt.savefig(os.path.join(printDir,printFn),format="png")
469 479
         
470 480
     plt.legend()
471 481
     plt.show()
482
+
483
+def get_pitches_and_takes( inDir ):
484
+
485
+    pitchD = {}
486
+    inDirL = os.listdir( inDir )
487
+
488
+    for pitch in inDirL:
489
+        path    = os.path.join( inDir, pitch )
490
+        takeIdL = os.listdir( path )
491
+        
492
+        takeIdL = sorted([ int(takeId) for takeId in takeIdL ])
493
+        takeIdL = [ str(x) for x in takeIdL ] 
494
+        pitchD[int(pitch)] = takeIdL
495
+
496
+
497
+    return pitchD
472 498
     
473 499
 def plot_us_db_takes( inDir, cfg, pitchL, printDir=""):
474 500
 
@@ -480,14 +506,11 @@ def plot_us_db_takes( inDir, cfg, pitchL, printDir=""):
480 506
 
481 507
 def plot_us_db_takes_last( inDir, cfg, pitchL, printDir ):
482 508
 
509
+    pitchD = get_pitches_and_takes(inDir)
510
+    
483 511
     takeIdL = []
484 512
     for pitch in pitchL:
485
-
486
-        inDirL = os.listdir( os.path.join(inDir,str(pitch)))
487
-
488
-        inDirL = sorted(inDirL)
489
-        
490
-        takeIdL.append( int(inDirL[-1]) )
513
+        takeIdL.append( int(pitchD[pitch][-1]) )
491 514
 
492 515
     return _plot_us_db_takes( inDir, cfg, pitchL, takeIdL, printDir, "us_db_takes_last.png")
493 516
     
@@ -535,17 +558,21 @@ def plot_all_noise_curves( inDir, cfg, pitchL=None ):
535 558
     plt.legend()
536 559
     plt.show()
537 560
 
538
-def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
561
+def nearest_sample_point( dbL, usL, db0, us0 ):
562
+    xL = np.array([ abs(us-us0)  for db,us in zip(dbL,usL) ])
539 563
 
540
-    pitchFolderL = os.listdir(inDir)
564
+    return np.argmin(xL)
541 565
 
542
-    print(pitchL)
566
+    
567
+def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
543 568
 
569
+    takeIdArg  = takeId
570
+    pitchTakeD = get_pitches_and_takes(inDir)
571
+    
572
+    pitchFolderL = os.listdir(inDir)
544 573
     if pitchL is None:
545 574
         pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
546 575
 
547
-    print(pitchL)
548
-        
549 576
     okL       = []
550 577
     outPitchL = []
551 578
     minDbL    = []
@@ -553,9 +580,11 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
553 580
     
554 581
     for midi_pitch in pitchL:
555 582
 
556
-        print(midi_pitch)
583
+        takeId = None
584
+        if takeIdArg == -1:
585
+            takeId = pitchTakeD[midi_pitch][-1]
557 586
 
558
-        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
587
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'], takeId )
559 588
 
560 589
         okL.append(False)
561 590
 
@@ -571,6 +600,9 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
571 600
         minDbL.append(elbow_db)
572 601
         outPitchL.append(midi_pitch)
573 602
 
603
+        smp_idx = nearest_sample_point( dbL, usL, elbow_db, elbow_us )
604
+
605
+        print(" %i:[-1,%i], " % (midi_pitch,smp_idx))
574 606
 
575 607
 
576 608
     p_dL = sorted( zip(outPitchL,minDbL,maxDbL,okL), key=lambda x: x[0] )
@@ -593,7 +625,9 @@ def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2, printDir=None ):
593 625
        
594 626
     plt.show()
595 627
 
596
-def plot_min_db_manual( inDir, cfg, printDir=None ):
628
+def plot_min_db_manual( inDir, cfg, printDir=None, absMaxDb=27, absMinDb=3 ):
629
+
630
+    pitchTakeD = get_pitches_and_takes(inDir)
597 631
     
598 632
     pitchL     = list(cfg.manualMinD.keys())
599 633
     
@@ -606,30 +640,40 @@ def plot_min_db_manual( inDir, cfg, printDir=None ):
606 640
 
607 641
     for midi_pitch in pitchL:
608 642
 
609
-        manual_take_id    = cfg.manualMinD[midi_pitch][0]
643
+        if cfg.manualLastFl:
644
+            manual_take_id = pitchTakeD[midi_pitch][-1]
645
+            takeId         = manual_take_id
646
+        else:
647
+            manual_take_id = cfg.manualMinD[midi_pitch][0]
648
+            takeId         = None
649
+        
610 650
         manual_sample_idx = cfg.manualMinD[midi_pitch][1]
611 651
         
612
-        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
652
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'], takeId )
613 653
 
614 654
         okL.append(False)
615 655
 
616
-        takeId = len(set(takeIdL))-1
656
+        if takeId is None:
657
+            takeId = len(set(takeIdL))-1
617 658
 
659
+            # most pitches have 3 sample takes that do not
660
+            if len(set(takeIdL)) == 3 and manual_take_id == takeId:
661
+                okL[-1] = True
662
+        else:
663
+            okL[-1] = True
664
+                
618 665
         # maxDb is computed on all takes (not just the specified take)
619 666
         db_maxL = sorted(dbL)
620
-        max_db = np.mean(db_maxL[-4:])
667
+        max_db = min(absMaxDb,np.mean(db_maxL[-4:]))
621 668
         maxDbL.append( max_db )
622 669
 
623 670
         # get the us,db values for the specified take
624 671
         usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==manual_take_id  ])
625 672
 
626
-        # most pitches have 3 sample takes that do not
627
-        if len(set(takeIdL)) == 3 and manual_take_id == takeId:
628
-            okL[-1] = True
629 673
 
630 674
         # min db from the sample index manually specified in cfg
631
-        manualMinDb = dbL[ manual_sample_idx ]
632
-        
675
+        manualMinDb = max(absMinDb,dbL[ manual_sample_idx ])
676
+
633 677
         minDbL.append( manualMinDb )
634 678
         outPitchL.append(midi_pitch)
635 679
 
@@ -642,8 +686,6 @@ def plot_min_db_manual( inDir, cfg, printDir=None ):
642 686
 
643 687
         
644 688
             
645
-    
646
-
647 689
     # Form the complete set of min/max db levels for each pitch by interpolating the
648 690
     # db values between the manually selected anchor points.
649 691
     interpMinDbL = np.interp( pitchL, cfg.manualAnchorPitchMinDbL, anchorMinDbL )
@@ -861,27 +903,67 @@ def report_take_ids( inDir ):
861 903
         if len(takeDirL) == 0:
862 904
             print(pitch," directory empty")
863 905
         else:
864
-            with open( os.path.join(pitchDir,'0','seq.json'), "rb") as f:
865
-                r = json.load(f)
906
+            
907
+            fn = os.path.join(pitchDir,'0','seq.json')
908
+            
909
+            if not os.path.isfile(fn):
910
+                print("Missing sequence file:",fn)
911
+            else:
912
+                with open( fn, "rb") as f:
913
+                    r = json.load(f)
866 914
 
867
-            if len(r['eventTimeL']) != 81:
868
-                print(pitch," ",len(r['eventTimeL']))
915
+                
916
+                if len(r['eventTimeL']) != 81:
917
+                    print(pitch," ",len(r['eventTimeL']))
918
+
919
+                if len(takeDirL) != 3:
920
+                    print("***",pitch,len(takeDirL))
921
+
922
+def filter_us_db( us0L, db0L ):
923
+
924
+    us1L = [us0L[-1]]
925
+    db1L = [db0L[-1]]
926
+    dDb = 0
927
+    lastIdx = 0
928
+    for i,(us,db) in enumerate(zip( us0L[::-1],db0L[::-1])):
929
+        db1 = db1L[-1]
930
+        if db < db1 and db1-db >= dDb/2:
931
+            dDb = db1 - db
932
+            us1L.append(us)
933
+            db1L.append(db)
934
+            lastIdx = i
869 935
 
870
-            if len(takeDirL) != 3:
871
-                print("***",pitch,len(takeDirL))
872 936
 
937
+    lastIdx = len(us0L) - lastIdx - 1
938
+
939
+    
940
+    usL = [ us0L[lastIdx] ]
941
+    dbL = [ db0L[lastIdx] ]
942
+    dDb = 0
943
+    for us,db in zip(us0L[lastIdx::],db0L[lastIdx::]):
944
+        db1 = dbL[-1]
945
+        if db > db1:
946
+            dDb = db-db1
947
+            usL.append(us)
948
+            dbL.append(db)
949
+        
950
+    return usL,dbL
951
+                                    
873 952
 def cache_us_db( inDir, cfg, outFn ):
874 953
 
954
+    pitchTakeD = get_pitches_and_takes(inDir)
955
+    
875 956
     pitch_usDbD = {}
876 957
     pitchDirL = os.listdir(inDir)
877 958
 
878
-    for pitch in pitchDirL:
959
+    for pitch,takeIdL in pitchTakeD.items():
879 960
 
880 961
         pitch = int(pitch)
962
+        takeId = takeIdL[-1]
881 963
 
882 964
         print(pitch)
883 965
         
884
-        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
966
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'], takeId )
885 967
 
886 968
         pitch_usDbD[pitch] = { 'usL':usL, 'dbL':dbL, 'durMsL':durMsL, 'takeIdL':takeIdL, 'holdDutyPctL': holdDutyPctL }
887 969
 
@@ -889,7 +971,7 @@ def cache_us_db( inDir, cfg, outFn ):
889 971
     with open(outFn,"w") as f:
890 972
         json.dump(pitch_usDbD,f)
891 973
 
892
-        
974
+    
893 975
             
894 976
 def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
895 977
 
@@ -897,28 +979,44 @@ def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
897 979
     
898 980
     pitchDirL = os.listdir(inDir)
899 981
 
982
+    # pitchUsDbD = { pitch: 
900 983
     with open(cacheFn,"r") as f:
901 984
         pitchUsDbD = json.load(f)
902 985
         
903 986
 
987
+    # form minMaxDb = { pitch:(minDb,maxDb) }
904 988
     with open("minInterpDb.json","r") as f:
905 989
         r = json.load(f)
906 990
         minMaxDbD = { pitch:(minDb,maxDb) for pitch,minDb,maxDb in zip(r['pitchL'],r['minDbL'],r['maxDbL']) }
907
-    
908 991
 
992
+        
909 993
     pitchL = sorted( [ int(pitch) for pitch in pitchUsDbD.keys()] )
910
-    
994
+
995
+    # for each pitch
911 996
     for pitch in pitchL:
997
+
998
+        # get the us/db map for this 
912 999
         d = pitchUsDbD[str(pitch)]
1000
+
1001
+
1002
+        usL, dbL = filter_us_db( d['usL'], d['dbL'] )        
1003
+        #usL = d['usL']
1004
+        #dbL = np.array(d['dbL'])
1005
+        dbL = np.array(dbL)
913 1006
         
914
-        usL = d['usL']
915
-        dbL = np.array(d['dbL'])
916 1007
 
917 1008
         velMapD[pitch] = []
918
-        
919
-        for i in range(dynLevelN+1):
920 1009
 
921
-            db = minMaxDbD[pitch][0] + (i * (minMaxDbD[pitch][1] - minMaxDbD[pitch][0])/ dynLevelN)
1010
+        maxDb = minMaxDbD[pitch][1]
1011
+        minDb = minMaxDbD[pitch][0]
1012
+
1013
+        dynLevelN = len(cfg.velTableDbL)
1014
+
1015
+        # for each dynamic level
1016
+        for i in range(dynLevelN):
1017
+
1018
+            #db = minDb + (i * (maxDb - minDb)/ dynLevelN)
1019
+            db = cfg.velTableDbL[i]
922 1020
 
923 1021
             usIdx = np.argmin( np.abs(dbL - db) )
924 1022
             
@@ -946,7 +1044,7 @@ def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
946 1044
     
947 1045
 if __name__ == "__main__":
948 1046
 
949
-    printDir = None #os.path.expanduser( "~/src/picadae_ac_3/doc")
1047
+    printDir = os.path.expanduser("~/temp") # os.path.expanduser( "~/src/picadae_ac_3/doc")
950 1048
     cfgFn   = sys.argv[1]
951 1049
     inDir   = sys.argv[2]
952 1050
     mode    = sys.argv[3]
@@ -981,7 +1079,7 @@ if __name__ == "__main__":
981 1079
     elif mode == 'manual_db':
982 1080
         plot_min_db_manual( inDir, cfg, printDir=printDir )
983 1081
     elif mode == 'gen_vel_map':
984
-        gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" )
1082
+        gen_vel_map( inDir, cfg, "minInterpDb.json", 12, "cache_us_db.json" )
985 1083
     elif mode == 'cache_us_db':
986 1084
         cache_us_db( inDir, cfg, "cache_us_db.json")
987 1085
     else:

+ 1
- 1
plot_us_db_range.ipynb View File

@@ -75,7 +75,7 @@
75 75
    "name": "python",
76 76
    "nbconvert_exporter": "python",
77 77
    "pygments_lexer": "ipython3",
78
-   "version": "3.7.5"
78
+   "version": "3.8.6"
79 79
   }
80 80
  },
81 81
  "nbformat": 4,

+ 4
- 1
rms_analysis.py View File

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

Loading…
Cancel
Save