Browse Source

Many changes and additions.

master
kpl 4 years ago
parent
commit
4d3044af67
13 changed files with 1481 additions and 182 deletions
  1. 147
    0
      MidiFilePlayer.py
  2. 81
    51
      calibrate.py
  3. 45
    0
      calibrate_plot.py
  4. 85
    0
      elbow.py
  5. 102
    30
      p_ac.py
  6. 128
    23
      p_ac.yml
  7. 1
    1
      plot_calibrate.py
  8. 598
    52
      plot_seq_1.py
  9. 22
    15
      plot_us_db_range.ipynb
  10. 56
    10
      rms_analysis.py
  11. 162
    0
      velMapD.h
  12. 1
    0
      velMapD.json
  13. 53
    0
      velTableToDataStruct.py

+ 147
- 0
MidiFilePlayer.py View File

@@ -0,0 +1,147 @@
1
+
2
+import json
3
+
4
+class MidiFilePlayer:
5
+    def __init__( self, cfg, api, midiDev, midiFn, velMapFn="velMapD.json" ):
6
+        self.cfg      = cfg
7
+        self.api      = api
8
+        self.midiDev  = midiDev
9
+        self.midiL    = []  # [ (us,status,d0,d1) ]
10
+
11
+        self._parse_midi_file(midiFn)
12
+        self.nextIdx = None
13
+        self.startMs  = 0
14
+        self.curDutyPctD = {}  # { pitch:duty } track the current hold duty cycle of each note
15
+        self.velMapD = {}
16
+        self.holdDutyPctD = cfg.calibrateArgs['holdDutyPctD']
17
+        
18
+        with open(velMapFn,'r') as f:
19
+            velMapD = json.load(f)
20
+
21
+            for pitch,usDbL in velMapD.items():
22
+                self.velMapD[ int(pitch) ] = usDbL
23
+
24
+    def start(self, ms):
25
+        self.nextIdx = 0
26
+        self.startMs = ms
27
+
28
+    def stop( self, ms):
29
+        self.nextIdx = None
30
+        for pitch in self.velMapD.keys():
31
+            self.api.note_off( int(pitch) )
32
+
33
+    def tick( self, ms):
34
+
35
+        if self.nextIdx is None:
36
+            return
37
+        
38
+        curOffsMs = ms - self.startMs
39
+        
40
+        while self.nextIdx < len(self.midiL):
41
+            
42
+            if curOffsMs < self.midiL[ self.nextIdx ][0]:
43
+                break
44
+            
45
+            cmd = self.midiL[ self.nextIdx ][1]
46
+            
47
+            if cmd == 'non':
48
+                self._note_on(self.midiL[ self.nextIdx ][2],self.midiL[ self.nextIdx ][3])
49
+            elif cmd == 'nof':
50
+                self._note_off(self.midiL[ self.nextIdx ][2])
51
+            elif cmd == 'ctl' and self.midiL[ self.nextIdx ][2] == 64:
52
+                self.midiDev.send_controller(64,self.midiL[ self.nextIdx ][3])
53
+                
54
+            self.nextIdx += 1
55
+            
56
+
57
+        if self.nextIdx >= len(self.midiL):
58
+            self.nextIdx = None
59
+            
60
+    def _get_duty_cycle( self, pitch, pulseUsec ):
61
+        
62
+        dutyPct = 50
63
+
64
+        if pitch in self.holdDutyPctD:
65
+            
66
+            dutyPct = self.holdDutyPctD[pitch][0][1]
67
+            for refUsec,refDuty in self.holdDutyPctD[pitch]:
68
+                print(pitch,refUsec,refDuty)
69
+                if pulseUsec < refUsec:
70
+                    break
71
+                dutyPct = refDuty
72
+
73
+        return dutyPct
74
+            
75
+    def _set_duty_cycle( self, pitch, pulseUsec ):
76
+
77
+        dutyPct = self._get_duty_cycle( pitch, pulseUsec )
78
+
79
+        if pitch not in self.curDutyPctD or self.curDutyPctD[pitch] != dutyPct:
80
+            self.curDutyPctD[pitch] = dutyPct
81
+            self.api.set_pwm_duty( pitch, dutyPct )
82
+            print("Hold Duty Set:",dutyPct)
83
+
84
+        return dutyPct
85
+
86
+    def _get_pulse_us( self, pitch, vel ):
87
+
88
+        usDbL = self.velMapD[pitch]
89
+        idx = round(vel * len(usDbL) / 127)
90
+        
91
+        if idx > len(usDbL):
92
+            idx = len(usDbL)-1
93
+            
94
+        us = usDbL[ idx ][0]
95
+
96
+        print('non',pitch,vel,idx,us)
97
+
98
+        return us
99
+    
100
+    def _note_on( self, pitch, vel ):
101
+
102
+        if pitch not in self.velMapD:
103
+            print("Missing pitch:",pitch)
104
+        else:
105
+            pulseUs = self._get_pulse_us(pitch,vel)
106
+            self._set_duty_cycle( pitch, pulseUs )
107
+            self.api.note_on_us( pitch, pulseUs )
108
+
109
+
110
+    def _note_off( self, pitch ):
111
+        self.api.note_off( pitch )
112
+
113
+
114
+    def _parse_midi_file( self,fn ):
115
+
116
+        with open(fn,"r") as f:
117
+
118
+            for lineNumb,line in enumerate(f):
119
+                if lineNumb >= 3:
120
+                    tokenL = line.split()
121
+
122
+                    if len(tokenL) > 5:
123
+                        usec   = int(tokenL[3])
124
+                        status = None
125
+                        d0     = None
126
+                        d1     = None
127
+                        if tokenL[5] == 'non' or tokenL[5]=='nof' or tokenL[5]=='ctl':
128
+                            status = tokenL[5]
129
+                            d0     = int(tokenL[7])
130
+                            d1     = int(tokenL[8])
131
+                            self.midiL.append( (usec/1000,status,d0,d1))
132
+
133
+            self.midiL = sorted( self.midiL, key=lambda x: x[0] )
134
+        
135
+
136
+
137
+if __name__ == "__main__":
138
+
139
+    midiFn = "/home/kevin/media/audio/midi/txt/988-v25.txt"
140
+
141
+    mfp = MidiFilePlayer(None,None,midiFn)
142
+                        
143
+    print(mfp.midiL[0:10])
144
+    
145
+    
146
+
147
+    

+ 81
- 51
calibrate.py View File

@@ -1,38 +1,41 @@
1 1
 import os,types,wave,json,array
2 2
 import numpy as np
3 3
 from rms_analysis import rms_analyze_one_rt_note
4
+from plot_seq_1 import get_merged_pulse_db_measurements
4 5
 
5 6
 class Calibrate:
6 7
     def __init__( self, cfg, audio, midi, api ):
7
-        self.cfg   = types.SimpleNamespace(**cfg)
8
-        self.audio = audio
9
-        self.midi  = midi
10
-        self.api   = api
11
-        self.state = "stopped"  # stopped | started | note_on | note_off | analyzing
8
+        self.cfg        = types.SimpleNamespace(**cfg)
9
+        self.audio      = audio
10
+        self.midi       = midi
11
+        self.api        = api
12
+        self.state      = "stopped"  # stopped | started | note_on | note_off | analyzing
12 13
         self.playOnlyFl = False
13
-        self.startMs = None
14
-        self.nextStateChangeMs = None
14
+        self.startMs    = None
15
+        self.nextStateChangeMs    = None
15 16
         self.curHoldDutyCyclePctD = None  # { pitch:dutyPct}
16
-        self.noteAnnotationL = []  # (noteOnMs,noteOffMs,pitch,pulseUs)
17
+        self.noteAnnotationL      = []  # (noteOnMs,noteOffMs,pitch,pulseUs)
17 18
 
18 19
         self.measD = None   # { midi_pitch: [ {pulseUs, db, durMs, targetDb } ] }
19 20
 
21
+        self.initPulseDbListD = self._get_init_pulseDbD()
22
+
20 23
         self.curNoteStartMs = None
21
-        self.curPitchIdx = None
24
+        self.curPitchIdx    = None
22 25
         self.curTargetDbIdx = None
23
-        self.successN = None
24
-        self.failN    = None
26
+        self.successN       = None
27
+        self.failN          = None
25 28
 
26
-        self.curTargetDb = None
27
-        self.curPulseUs = None
28
-        self.curMatchN = None
29
-        self.curAttemptN = None
29
+        self.curTargetDb        = None
30
+        self.curPulseUs         = None
31
+        self.curMatchN          = None
32
+        self.curAttemptN        = None
30 33
         self.lastAudiblePulseUs = None
31 34
         self.maxTooShortPulseUs = None
32
-        self.pulseDbL = None
33
-        self.deltaUpMult = None
34
-        self.deltaDnMult = None
35
-        self.skipMeasFl  = None
35
+        self.pulseDbL           = None
36
+        self.deltaUpMult        = None
37
+        self.deltaDnMult        = None
38
+        self.skipMeasFl         = None
36 39
         
37 40
     def start(self,ms):
38 41
         self.stop(ms)
@@ -42,22 +45,23 @@ class Calibrate:
42 45
         
43 46
         self.startMs           = ms
44 47
 
45
-        self.curPitchIdx    = 0
46
-        self.curPulseUs     = self.cfg.initPulseUs
48
+        self.curPitchIdx        = 0
49
+        self.curPulseUs         = self.cfg.initPulseUs
47 50
         self.lastAudiblePulseUs = None
48 51
         self.maxTooShortPulseUs = None
49
-        self.pulseDbL = []
50
-        self.deltaUpMult = 1
51
-        self.deltaDnMult = 1
52
-        self.curTargetDbIdx = -1       
52
+        self.pulseDbL           = []
53
+        self.pulseDbL           = self.initPulseDbListD[ self.cfg.pitchL[ self.curPitchIdx ] ]
54
+        self.deltaUpMult        = 1
55
+        self.deltaDnMult        = 1
56
+        self.curTargetDbIdx     = -1       
53 57
         self._start_new_db_target()
54 58
         
55 59
         self.curDutyPctD = {}
56
-        self.skipMeasFl = False
57
-        self.measD = {}
60
+        self.skipMeasFl  = False
61
+        self.measD       = {}
58 62
         
59 63
         self.successN = 0
60
-        self.failN = 0
64
+        self.failN    = 0
61 65
         self.audio.record_enable(True)
62 66
 
63 67
     def stop(self,ms):
@@ -84,7 +88,7 @@ class Calibrate:
84 88
             
85 89
             self.audio.record_enable(True)
86 90
 
87
-            self._do_play_update()
91
+            self._do_play_only_update()
88 92
         
89 93
     def tick(self,ms):
90 94
 
@@ -105,7 +109,7 @@ class Calibrate:
105 109
 
106 110
             elif self.state == 'note_off':
107 111
                 if self.playOnlyFl:
108
-                    if not self._do_play_update():
112
+                    if not self._do_play_only_update():
109 113
                         self.stop(ms)
110 114
                         self.state = 'stopped'
111 115
                 else:
@@ -120,7 +124,7 @@ class Calibrate:
120 124
                     self.state = 'started'
121 125
                                     
122 126
 
123
-    def _calc_play_pulse_us( self, pitch, targetDb ):
127
+    def _calc_play_only_pulse_us( self, pitch, targetDb ):
124 128
 
125 129
         pulseDbL = []
126 130
         for d in self.measD[ pitch ]:
@@ -136,7 +140,7 @@ class Calibrate:
136 140
         
137 141
         return np.mean(pulseL)
138 142
                 
139
-    def _do_play_update( self ):
143
+    def _do_play_only_update( self ):
140 144
 
141 145
         if self.curPitchIdx >= 0:
142 146
             self._meas_note( self.cfg.pitchL[self.curPitchIdx], self.curPulseUs )
@@ -150,7 +154,7 @@ class Calibrate:
150 154
 
151 155
         pitch    = self.cfg.pitchL[ self.curPitchIdx ]
152 156
         targetDb = self.cfg.targetDbL[ self.curTargetDbIdx ]
153
-        self.curPulseUs  = self._calc_play_pulse_us( pitch, targetDb )
157
+        self.curPulseUs  = self._calc_play_only_pulse_us( pitch, targetDb )
154 158
         self.curTargetDb = targetDb
155 159
 
156 160
         if self.curPulseUs == -1:
@@ -161,7 +165,24 @@ class Calibrate:
161 165
         
162 166
         return True
163 167
             
164
-    
168
+    def _get_init_pulseDbD( self ):
169
+
170
+        initPulseDbListD = {}
171
+
172
+        print("Calculating initial calibration search us/db lists ...")
173
+        if self.cfg.inDir is not None:
174
+        
175
+            for pitch in self.cfg.pitchL:
176
+
177
+                print(pitch)
178
+
179
+                inDir = os.path.expanduser( self.cfg.inDir )
180
+
181
+                usL,dbL,_,_,_ = get_merged_pulse_db_measurements( inDir, pitch, self.cfg.analysisD )
182
+
183
+                initPulseDbListD[pitch] = [ (us,db) for us,db in zip(usL,dbL) ]
184
+
185
+        return initPulseDbListD
165 186
         
166 187
     def _get_duty_cycle( self, pitch, pulseUsec ):
167 188
         
@@ -232,8 +253,9 @@ class Calibrate:
232 253
 
233 254
         return int(round(curPulse + np.sign(targetDb - curDb) * delta_pulse))
234 255
 
235
-    def _step( self, targetDb, dbL, pulseL ):
256
+    def _step( self, targetDb ):
236 257
 
258
+        # get the last two pulse/db samples
237 259
         pulse0,db0 = self.pulseDbL[-2]
238 260
         pulse1,db1 = self.pulseDbL[-1] 
239 261
 
@@ -255,19 +277,22 @@ class Calibrate:
255 277
     
256 278
     def _calc_next_pulse_us( self, targetDb ):
257 279
 
280
+        
258 281
         # sort pulseDb ascending on db
259
-        #self.pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
282
+        pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
260 283
 
261
-        
262
-        pulseL,dbL = zip(*self.pulseDbL)
284
+        # get the set of us/db values tried so far
285
+        pulseL,dbL = zip(*pulseDbL)
263 286
 
264 287
         max_i = np.argmax(dbL)
265 288
         min_i = np.argmin(dbL)        
266 289
 
290
+        # if the targetDb is greater than the max. db value achieved so far
267 291
         if targetDb > dbL[max_i]:
268 292
             pu =  pulseL[max_i] + self.deltaUpMult * 500
269 293
             self.deltaUpMult += 1
270 294
 
295
+        # if the targetDb is less than the min. db value achieved so far
271 296
         elif targetDb < dbL[min_i]:
272 297
             pu = pulseL[min_i] - self.deltaDnMult * 500
273 298
             self.deltaDnMult += 1
@@ -277,12 +302,18 @@ class Calibrate:
277 302
                 pu = self.maxTooShortPulseUs + (abs(pulseL[min_i] - self.maxTooShortPulseUs))/2
278 303
                 self.deltaDnMult = 1                
279 304
         else:
305
+            # the targetDb value is inside the min/max range of the db values acheived so far
280 306
             self.deltaUpMult = 1
281 307
             self.deltaDnMult = 1
308
+
309
+            # interpolate the new pulse value based on the values seen so far
310
+
311
+            # TODO: use only closest 5 values rather than all values
282 312
             pu =  np.interp([targetDb],dbL,pulseL)
283 313
 
314
+            # the selected pulse has already been sampled
284 315
             if int(pu) in pulseL:
285
-                pu = self._step(targetDb, dbL, pulseL )
316
+                pu = self._step(targetDb )
286 317
             
287 318
 
288 319
         return max(min(pu,self.cfg.maxPulseUs),self.cfg.minPulseUs)
@@ -319,23 +350,23 @@ class Calibrate:
319 350
                 
320 351
             else:
321 352
 
322
-                # this is a valid measurement store it to the pulse-db table 
353
+                # this is a valid measurement, store it to the pulse-db table 
323 354
                 self.pulseDbL.append( (self.curPulseUs,db) )
324 355
 
325
-                # track the most recent audible note - to return to if a successive note is too short
356
+                # track the most recent audible note (to return to if a successive note is too short)
326 357
                 self.lastAudiblePulseUs = self.curPulseUs
327 358
                 
328 359
                 # calc the upper and lower bounds db range
329 360
                 lwr_db = self.curTargetDb * ((100.0 - self.cfg.tolDbPct)/100.0)
330 361
                 upr_db = self.curTargetDb * ((100.0 + self.cfg.tolDbPct)/100.0)
331 362
 
332
-                # was this note is inside the db range then set the 'match' flag
363
+                # if this note was inside the db range then set the 'match' flag
333 364
                 if lwr_db <= db and db <= upr_db:
334 365
                     self.curMatchN += 1
335 366
                     measD['matchFl'] = True
336 367
                     print("MATCH!")
337 368
 
338
-                # 
369
+                # calculate the next pulse length
339 370
                 self.curPulseUs = int(self._calc_next_pulse_us(self.curTargetDb))
340 371
 
341 372
             # if at least minMatchN matches have been made on this pitch/targetDb 
@@ -371,8 +402,6 @@ class Calibrate:
371 402
 
372 403
             sigV = buf_result.value
373 404
 
374
-            
375
-
376 405
             # get the annotated begin and end of the note as sample indexes into sigV
377 406
             bi = int(round(annD['beg_ms'] * self.audio.srate / 1000))
378 407
             ei = int(round(annD['end_ms'] * self.audio.srate / 1000))
@@ -384,7 +413,6 @@ class Calibrate:
384 413
             bi = max(0,bi - noteOffSmp_o_2)
385 414
             ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1)
386 415
 
387
-            
388 416
             ar = types.SimpleNamespace(**self.cfg.analysisD)
389 417
 
390 418
             # shift the annotatd begin/end of the note to be relative to  index bi
@@ -393,9 +421,10 @@ class Calibrate:
393 421
 
394 422
             #print("MEAS:",begMs,endMs,bi,ei,sigV.shape,self.audio.is_recording_enabled(),ar)
395 423
 
424
+            
396 425
 
397 426
             # analyze the note
398
-            resD  = rms_analyze_one_rt_note( sigV[bi:ei], self.audio.srate, begMs, endMs, midi_pitch, rmsWndMs=ar.rmsWndMs, rmsHopMs=ar.rmsHopMs, dbRefWndMs=ar.dbRefWndMs, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct )
427
+            resD  = rms_analyze_one_rt_note( sigV[bi:ei], self.audio.srate, begMs, endMs, midi_pitch, rmsWndMs=ar.rmsWndMs, rmsHopMs=ar.rmsHopMs, dbLinRef=ar.dbLinRef, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct )
399 428
 
400 429
             resD["pulse_us"]   = pulse_us 
401 430
             resD["midi_pitch"] = midi_pitch
@@ -405,9 +434,8 @@ class Calibrate:
405 434
             resD['matchFl']    = False
406 435
             resD['targetDb']   = self.curTargetDb
407 436
             resD['annIdx']     = len(self.noteAnnotationL)-1
408
-            
409
-            print( "%4.1f hm:%4.1f (%4.1f) %4i  td:%4.1f (%4.1f) %4i" %  (self.curTargetDb,resD['hm']['db'], resD['hm']['db']-self.curTargetDb, resD['hm']['durMs'], resD['td']['db'], resD['td']['db']-self.curTargetDb, resD['td']['durMs']))
410 437
 
438
+            print( "%4.1f hm:%4.1f (%4.1f) %4i  td:%4.1f (%4.1f) %4i" %  (self.curTargetDb,resD['hm']['db'], resD['hm']['db']-self.curTargetDb, resD['hm']['durMs'], resD['td']['db'], resD['td']['db']-self.curTargetDb, resD['td']['durMs']))
411 439
         
412 440
         return resD
413 441
 
@@ -427,13 +455,15 @@ class Calibrate:
427 455
             if self.curPitchIdx >= len(self.cfg.pitchL):
428 456
                 return False
429 457
 
430
-            
458
+
459
+        # reset the variables prior to begining the next target search
431 460
         self.curTargetDb        = self.cfg.targetDbL[ self.curTargetDbIdx ]
432 461
         self.curMatchN          = 0
433 462
         self.curAttemptN        = 0
434 463
         self.lastAudiblePulseUs = None
435 464
         self.maxTooShortPulseUs = None
436
-        self.pulseDbL           = []
465
+        self.pulseDbL = []
466
+        self.pulseDbL           = self.initPulseDbListD[ self.cfg.pitchL[ self.curPitchIdx ] ]
437 467
         self.deltaUpMult        = 1
438 468
         self.deltaDnMult        = 1
439 469
         return True

+ 45
- 0
calibrate_plot.py View File

@@ -0,0 +1,45 @@
1
+import os,sys,json
2
+import common
3
+import matplotlib.pyplot as plt
4
+import plot_seq_1
5
+
6
+def plot_calibrate( cfg, pitch, dataD ):
7
+
8
+    dataL = [ (d['pulse_us'], d['hm']['db'], d['targetDb'], d['matchFl'],d['skipMeasFl'],d['annIdx']) for d in dataD['measD'][pitch] ]
9
+
10
+    udmL = [(t[0],t[1],t[3]) for t in dataL]
11
+    udmL = sorted( udmL, key=lambda x: x[0] )
12
+    usL,dbL,matchL = zip(*udmL)
13
+
14
+    fig,ax = plt.subplots()
15
+
16
+    musL = [us for us,db,m in udmL if m]
17
+    mdbL = [db for us,db,m in udmL if m]
18
+
19
+    ax.plot(musL,mdbL,marker='o',color='red',linestyle='None')
20
+        
21
+    ax.plot(usL,dbL,marker='.')
22
+    
23
+    initDataDir = os.path.expanduser(dataD['cfg']['inDir'])
24
+    usL,dbL,_,_,_ = plot_seq_1.get_merged_pulse_db_measurements( initDataDir, int(pitch), cfg['analysisD'] )
25
+    
26
+
27
+    ax.plot(usL,dbL,marker='.')
28
+
29
+    plt.show()
30
+
31
+if __name__ == "__main__":
32
+
33
+    inDir = sys.argv[1]
34
+    cfgFn = sys.argv[2]
35
+    pitch = sys.argv[3]
36
+
37
+    cfg = common.parse_yaml_cfg(cfgFn)
38
+    cfg = cfg.calibrateArgs
39
+
40
+    dataFn = os.path.join(inDir,"meas.json")
41
+    with open(dataFn,"r") as f:
42
+        dataD = json.load(f)
43
+        
44
+    print("pitchL:",dataD['cfg']['pitchL'],"targetDbL:",dataD['cfg']['targetDbL'])
45
+    plot_calibrate( cfg, pitch, dataD )

+ 85
- 0
elbow.py View File

@@ -0,0 +1,85 @@
1
+
2
+import sys,os
3
+import numpy as np
4
+import common
5
+import rms_analysis
6
+
7
+
8
+
9
+def fit_points_to_reference( usL, dbL, usRefL, dbRefL ):
10
+
11
+    dbV = None
12
+    
13
+    yyL = [ (db,dbRefL[ usRefL.index(us)]) for i,(us,db) in enumerate(zip(usL,dbL)) if us in usRefL ]
14
+
15
+    if len(yyL) < 10:
16
+        print("NO FIT")
17
+    else:
18
+
19
+        y0L,yrL = zip(*yyL)
20
+        yN = len(y0L)
21
+        
22
+        A = np.vstack([np.ones(yN),y0L]).T
23
+        c,m = np.linalg.lstsq(A,yrL,rcond=None)[0]
24
+
25
+        dbV = (np.array(dbL) * m) + c
26
+
27
+
28
+    return dbV
29
+
30
+def find_elbow( usL, dbL, pointsPerLine=10 ):
31
+    
32
+    ppl_2         = int(pointsPerLine/2)
33
+    dL = []
34
+
35
+    
36
+    i = pointsPerLine
37
+
38
+    # for each consecutive set of 'pointsPerLine' points in usL and dbL
39
+    while i < len(usL):
40
+
41
+        # find the x,y coordinates of the first 'ppl_2' coordinates
42
+        x0L = np.array([ (us,1.0) for us in usL[i-pointsPerLine:i-ppl_2] ])
43
+        y0L = np.array(usL[i-pointsPerLine:i-ppl_2])
44
+
45
+        # find the x,y coordinates of the second 'ppl_2' coordinates
46
+        x1L = np.array([ (us,1.0) for us in usL[i-ppl_2:i]])
47
+        y1L = np.array(dbL[i-ppl_2:i])
48
+
49
+        
50
+        m0,c0   = np.linalg.lstsq(x0L,y0L,rcond=None)[0] # fit a line through the first set of points
51
+        m1,c1   = np.linalg.lstsq(x1L,y1L,rcond=None)[0] # fit a line through the second set of points
52
+
53
+        # store the angle between the two lines
54
+        dL.append(m1-m0)
55
+
56
+        i += 1
57
+
58
+    # find the max angle
59
+    i = np.argmax( dL )    
60
+
61
+    # return the x,y coordinate of the first data point of the second line
62
+    return (usL[i+ppl_2],dbL[i+ppl_2])
63
+    
64
+def find_elbow_main( cfg, inDir, midi_pitch, takeId ):
65
+
66
+    inDir         = os.path.join(inDir,str(pitch),str(takeId))
67
+    analysisArgsD = cfg.analysisArgs['rmsAnalysArgs']
68
+     
69
+    r = rms_analysis_main( inDir, int(midi_pitch), **analysisD )
70
+
71
+    usL = r.pkUsL
72
+    dbL = r.pkDbL
73
+    
74
+    return find_elbow(r.pkUsL,r.pkDbL)
75
+
76
+
77
+if __name__ == "__main__":
78
+
79
+    inDir = sys.argv[1]
80
+    cfgFn = sys.argv[2]
81
+    pitch = sys.argv[3]
82
+    
83
+    cfg = common.parse_yaml_cfg(cfgFn)
84
+
85
+    find_elbow( cfg, inDir, pitch, 0 )

+ 102
- 30
p_ac.py View File

@@ -10,11 +10,13 @@ from MidiDevice   import MidiDevice
10 10
 from result       import Result
11 11
 from common       import parse_yaml_cfg
12 12
 from plot_seq     import form_resample_pulse_time_list
13
-from plot_seq     import get_resample_points_wrap
13
+from plot_seq_1   import get_resample_points_wrap
14 14
 from plot_seq     import form_final_pulse_list
15 15
 from rt_note_analysis import RT_Analyzer
16 16
 from keyboard         import Keyboard
17 17
 from calibrate        import Calibrate
18
+from rms_analysis import rms_analyze_one_rt_note_wrap
19
+from MidiFilePlayer import MidiFilePlayer
18 20
 
19 21
 class AttackPulseSeq:
20 22
     """ Sequence a fixed pitch over a list of attack pulse lengths."""
@@ -29,7 +31,8 @@ class AttackPulseSeq:
29 31
         self.noteDurMs   = noteDurMs      # duration of each chord in milliseconds
30 32
         self.pauseDurMs  = pauseDurMs     # duration between end of previous note and start of next
31 33
         self.holdDutyPctL= None           # hold voltage duty cycle table [ (minPulseSeqUsec,dutyCyclePct) ]
32
-        
34
+        self.holdDutyPctD= None          # { us:dutyPct } for each us in self.pulseUsL
35
+        self.silentNoteN = None 
33 36
         self.pulse_idx            = 0     # Index of next pulse 
34 37
         self.state                = None  # 'note_on','note_off'
35 38
         self.prevHoldDutyPct      = None
@@ -39,11 +42,13 @@ class AttackPulseSeq:
39 42
         self.playOnlyFl           = False
40 43
         self.rtAnalyzer           = RT_Analyzer()
41 44
 
42
-    def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, playOnlyFl=False ):
45
+    def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, holdDutyPctD, playOnlyFl=False ):
43 46
         self.outDir     = outDir         # directory to write audio file and results
44 47
         self.pitch     = pitch           # note to play
45 48
         self.pulseUsL   = pulseUsL       # one onset pulse length in microseconds per sequence element
46 49
         self.holdDutyPctL = holdDutyPctL
50
+        self.holdDutyPctD = holdDutyPctD
51
+        self.silentNoteN = 0 
47 52
         self.pulse_idx  = 0
48 53
         self.state      = 'note_on'
49 54
         self.prevHoldDutyPct = None
@@ -83,18 +88,40 @@ class AttackPulseSeq:
83 88
         
84 89
             # if waiting to turn a note off
85 90
             elif self.state == 'note_off':
86
-                self._note_off(ms)                
91
+                self._note_off(ms)
92
+                self._count_silent_notes()
93
+                    
94
+                    
87 95
                 self.pulse_idx += 1
88 96
                 
89 97
                 # if all notes have been played
90
-                if self.pulse_idx >= len(self.pulseUsL):
98
+                if self.pulse_idx >= len(self.pulseUsL): # or self.silentNoteN >= self.cfg.maxSilentNoteCount:
91 99
                     self.stop(ms)
92 100
                     
93 101
             else:                
94 102
                 assert(0)
95 103
                     
104
+    def _count_silent_notes( self ):
105
+        annBegMs = self.eventTimeL[ self.pulse_idx ][0]
106
+        annEndMs = self.eventTimeL[ self.pulse_idx ][1]
107
+        minDurMs = self.cfg.silentNoteMinDurMs
108
+        maxPulseUs = self.cfg.silentNoteMaxPulseUs
109
+        
110
+        resD = rms_analyze_one_rt_note_wrap( self.audio, annBegMs, annEndMs, self.pitch, self.pauseDurMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
111
+
112
+        print( "         %4.1f db  %4i ms %i" %  (resD['hm']['db'], resD['hm']['durMs'], self.pulse_idx))
113
+
114
+        if resD is not None and resD['hm']['durMs'] < minDurMs and self.pulseUsL[ self.pulse_idx ] < maxPulseUs:
115
+            self.silentNoteN += 1
116
+            print("SILENT", self.silentNoteN)
117
+        else:
118
+            self.silentNoteN = 0
96 119
 
120
+        return self.silentNoteN
121
+        
97 122
     def _get_duty_cycle( self, pulseUsec ):
123
+        return self.holdDutyPctD[ pulseUsec ]
124
+    
98 125
         dutyPct = self.holdDutyPctL[0][1]
99 126
         for refUsec,refDuty in self.holdDutyPctL:
100 127
             if pulseUsec < refUsec:
@@ -232,7 +259,7 @@ class CalibrateKeys:
232 259
             print(outDir_id,outDir)
233 260
             
234 261
             # if this is not the first time this note has been sampled then get the resample locations
235
-            if outDir_id == 0:
262
+            if (outDir_id == 0) or self.cfg.useFullPulseListFl:
236 263
                 self.pulseUsL = self.cfg.full_pulseL
237 264
             else:
238 265
                 #self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs )
@@ -252,8 +279,23 @@ class CalibrateKeys:
252 279
                 if not os.path.isdir(outDir):
253 280
                     os.mkdir(outDir)
254 281
 
282
+            #------------------------
283
+            j = 0
284
+            holdDutyPctD = {}
285
+            for us in self.pulseUsL:
286
+
287
+                if j+1<len(holdDutyPctL) and us >= holdDutyPctL[j+1][0]:
288
+                    j += 1
289
+                    
290
+                    
291
+                holdDutyPctD[ us ] = holdDutyPctL[j][1]                
292
+            #------------------------
293
+
294
+            if self.cfg.reversePulseListFl:
295
+                self.pulseUsL = [ us for us in reversed(self.pulseUsL) ]
296
+                    
255 297
             # start the sequencer
256
-            self.seq.start( ms, outDir, pitch, self.pulseUsL, holdDutyPctL, playOnlyFl )
298
+            self.seq.start( ms, outDir, pitch, self.pulseUsL, holdDutyPctL, holdDutyPctD, playOnlyFl )
257 299
         
258 300
 
259 301
     def _calc_next_out_dir_id( self, outDir ):
@@ -268,12 +310,13 @@ class CalibrateKeys:
268 310
 # This is the main application API it is running in a child process.
269 311
 class App:
270 312
     def __init__(self ):
271
-        self.cfg       = None
272
-        self.audioDev  = None
273
-        self.api       = None
274
-        self.cal_keys  = None
275
-        self.keyboard  = None
276
-        self.calibrate = None
313
+        self.cfg            = None
314
+        self.audioDev       = None
315
+        self.api            = None
316
+        self.cal_keys       = None
317
+        self.keyboard       = None
318
+        self.calibrate      = None
319
+        self.midiFilePlayer = None
277 320
         
278 321
     def setup( self, cfg ):
279 322
         self.cfg = cfg
@@ -281,17 +324,22 @@ class App:
281 324
         self.audioDev = AudioDevice()
282 325
         self.midiDev = MidiDevice()
283 326
 
327
+        res = None
328
+        
284 329
         #
285 330
         # TODO: unify the result error handling
286 331
         # (the API and the audio device return two diferent 'Result' types
287 332
         #
288
-        
289
-        res = self.audioDev.setup(**cfg.audio)
333
+        if hasattr(cfg,'audio'):
334
+            res = self.audioDev.setup(**cfg.audio)
290 335
 
291
-        if not res:
292
-            self.audio_dev_list(0)
336
+            if not res:
337
+                self.audio_dev_list(0)
338
+            
293 339
         else:
340
+            self.audioDev = None
294 341
 
342
+        if True:
295 343
             if hasattr(cfg,'midi'):
296 344
                 res = self.midiDev.setup(**cfg.midi)
297 345
 
@@ -300,28 +348,31 @@ class App:
300 348
             else:
301 349
                 self.midiDev = None
302 350
 
303
-                self.api = Picadae( key_mapL=cfg.key_mapL)
351
+            self.api = Picadae( key_mapL=cfg.key_mapL)
304 352
 
305
-                # wait for the letter 'a' to come back from the serial port
306
-                api_res = self.api.wait_for_serial_sync(timeoutMs=cfg.serial_sync_timeout_ms)
353
+            # wait for the letter 'a' to come back from the serial port
354
+            api_res = self.api.wait_for_serial_sync(timeoutMs=cfg.serial_sync_timeout_ms)
307 355
 
308
-                # did the serial port sync fail?
309
-                if not api_res:
310
-                    res.set_error("Serial port sync failed.")
311
-                else:
312
-                    print("Serial port sync'ed")
356
+            # did the serial port sync fail?
357
+            if not api_res:
358
+                res.set_error("Serial port sync failed.")
359
+            else:
360
+                print("Serial port sync'ed")
361
+
362
+                self.cal_keys = CalibrateKeys( cfg, self.audioDev, self.api )
313 363
 
314
-                    self.cal_keys = CalibrateKeys( cfg, self.audioDev, self.api )
364
+                self.keyboard  = Keyboard( cfg, self.audioDev, self.api )
315 365
 
316
-                    self.keyboard  = Keyboard( cfg, self.audioDev, self.api )
366
+                self.calibrate = None #Calibrate( cfg.calibrateArgs, self.audioDev, self.midiDev, self.api )
317 367
 
318
-                    self.calibrate = Calibrate( cfg.calibrateArgs, self.audioDev, self.midiDev, self.api )
368
+                self.midiFilePlayer = MidiFilePlayer( cfg, self.api, self.midiDev, cfg.midiFileFn )
319 369
     
320 370
         return res
321 371
 
322 372
     def tick( self, ms ):
323
-        
324
-        self.audioDev.tick(ms)
373
+
374
+        if self.audioDev is not None:
375
+            self.audioDev.tick(ms)
325 376
         
326 377
         if self.cal_keys:
327 378
             self.cal_keys.tick(ms)
@@ -332,6 +383,9 @@ class App:
332 383
         if self.calibrate:
333 384
             self.calibrate.tick(ms)
334 385
 
386
+        if self.midiFilePlayer:
387
+            self.midiFilePlayer.tick(ms)
388
+
335 389
     def audio_dev_list( self, ms ):
336 390
         portL = self.audioDev.get_port_list( True )
337 391
 
@@ -382,6 +436,20 @@ class App:
382 436
         self.cal_keys.stop(ms)
383 437
         self.keyboard.stop(ms)
384 438
         self.calibrate.stop(ms)
439
+
440
+    def midi_file_player_start( self, ms ):
441
+        self.midiFilePlayer.start(ms)
442
+
443
+    def midi_file_player_stop( self, ms ):
444
+        self.midiFilePlayer.stop(ms)
445
+
446
+    def pedal_down( self, ms ):
447
+        print("pedal_down")
448
+        self.midiDev.send_controller(64, 100 )
449
+
450
+    def pedal_up( self, ms ):
451
+        print("pedal_up");
452
+        self.midiDev.send_controller(64, 0 )
385 453
         
386 454
     def quit( self, ms ):
387 455
         if self.api:
@@ -513,6 +581,10 @@ class Shell:
513 581
             'r':{ "func":"keyboard_repeat_pulse_idx", "minN":1,  "maxN":1, "help":"Repeat pulse index across keyboard with new pulse_idx"},
514 582
             'K':{ "func":"keyboard_start_target_db",  "minN":3,  "maxN":3, "help":"Play db across keyboard"},
515 583
             'R':{ "func":"keyboard_repeat_target_db", "minN":1,  "maxN":1, "help":"Repeat db across keyboard with new pulse_idx"},
584
+            'F':{ "func":"midi_file_player_start",    "minN":0,  "maxN":0, "help":"Play the MIDI file."},
585
+            'f':{ "func":"midi_file_player_stop",     "minN":0,  "maxN":0, "help":"Stop the MIDI file."},
586
+            'P':{ "func":"pedal_down",                "minN":0,  "maxN":0, "help":"Pedal down."},
587
+            'U':{ "func":"pedal_up",                  "minN":0,  "maxN":0, "help":"Pedal up."},
516 588
             }
517 589
 
518 590
     def _help( self, _=None ):

+ 128
- 23
p_ac.yml View File

@@ -3,19 +3,19 @@
3 3
 
4 4
 
5 5
     # Audio device setup
6
-    audio: {
6
+    audio_off: {
7 7
       inPortLabel: "5 USB Audio CODEC:", #"HDA Intel PCH: CS4208", # "5 USB Audio CODEC:", #"5 USB Sound Device",
8 8
       outPortLabel: ,
9 9
     },
10 10
 
11
-    midi_off: {
11
+    midi: {
12 12
         inMonitorFl: False,
13 13
         outMonitorFl: False,
14 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 21
     # Picadae API args
@@ -27,10 +27,19 @@
27 27
 
28 28
 
29 29
     # MeasureSeq args
30
-    outDir: "~/temp/p_ac_3e",
31
-    noteDurMs: 1000,
32
-    pauseDurMs: 1000,
33
-    #holdDutyPctL: [ [0,50], [22000,55] ],
30
+    outDir: "~/temp/p_ac_3g",
31
+    noteDurMs: 500,
32
+    pauseDurMs: 500,
33
+    reversePulseListFl: True,
34
+    useFullPulseListFl: True,
35
+    maxSilentNoteCount: 4,
36
+    silentNoteMaxPulseUs: 15000,
37
+    silentNoteMinDurMs: 250,
38
+
39
+    # Midi file player
40
+    midiFileFn: "/home/kevin/media/audio/midi/txt/round4.txt",
41
+    
42
+    
34 43
 
35 44
     full_pulse0L: [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 8000, 9000, 10000, 12000, 14000, 18000, 22000, 26000, 30000, 34000, 40000],
36 45
     full_pulse1L: [  10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 20000, 22000, 24000, 26000, 30000, 32000, 34000, 36000, 40000],
@@ -44,11 +53,18 @@
44 53
 
45 54
     full_pulse6L: [  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 ],    
46 55
 
47
-    full_pulseL: [  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 ],    
56
+    full_pulseMainL: [  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 ],    
48 57
 
49
-    full_pulse7L: [  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 ],    
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
+    
60
+
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 ],    
50 64
 
51
-    full_pulse9L: [  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 ],    
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
+    
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 ],
52 68
     
53 69
     # RMS analysis args
54 70
     analysisArgs: {
@@ -61,8 +77,8 @@
61 77
         durDecayPct: 40,  # percent drop in RMS to indicate the end of a note
62 78
       },
63 79
 
64
-      resampleMinDb: 10.0,           # note's less than this will be skipped
65
-      resampleNoiseLimitPct: 1.0,    # 
80
+      resampleMinDb: 7.0,            # note's less than this will be skipped
81
+      resampleNoiseLimitPct: 5.0,    # 
66 82
       resampleMinDurMs: 800,         # notes's whose duration is less than this will be skipped
67 83
       
68 84
       minAttkDb: 7.0,   # threshold of silence level 
@@ -76,11 +92,98 @@
76 92
       rmsAnalysisCacheFn: "/home/kevin/temp/rms_analysis_cache.pickle"
77 93
       },
78 94
 
95
+      manualMinD: {
96
+        23: [2, 24],
97
+        24: [2, 18],
98
+        25: [2, 41],
99
+        26: [2, 26], #
100
+        27: [2, 35], # (36 is an outlier)
101
+        28: [2, 35], # /36 (resample?)
102
+        29: [2, 22], # /23 (resample?)
103
+        30: [2, 28], # /29
104
+        31: [2, 39], #
105
+        32: [2, 27], #
106
+        33: [2, 10], #
107
+        34: [2, 27], # (29 outlier)
108
+        35: [2, 15], #
109
+        36: [2, 16], # ngz: (0 32 36) (1 31 36)
110
+        37: [2, 18], #
111
+        38: [2, 33], #
112
+        39: [2, 18], #
113
+        40: [2,  6], # ngz: (0 25 41)
114
+        41: [2, 22], # ngz: (2 9  22)
115
+        42: [2, 11], #
116
+        43: [2,  7],   #(8 outlier)], #
117
+        44: [2, 19],
118
+        45: [4,  7],   # 5 sample traes
119
+        46: [2,  4],
120
+        47: [2, 11],  # /12
121
+        48: [2, 27],  # /28
122
+        49: [2, 12],
123
+        50: [2,  6],
124
+        51: [2, 14],
125
+        52: [2, 26],
126
+        53: [3, 24 ], # ngz at onset
127
+        54: [2, 21],  # /22
128
+        55: [2, 10],  # /11
129
+        56: [2,  5],
130
+        57: [2,  6],
131
+        58: [2, 11],
132
+        59: [2,  5],
133
+        60: [2, 13],
134
+        61: [4,  5],
135
+        62: [2,  7],
136
+        63: [2, 12],
137
+        64: [3, 33],
138
+        65: [2, 23],
139
+        66: [2, 36],
140
+        67: [2, 16],
141
+        68: [2,  1],  # needs decreased start us
142
+        69: [1,  7],
143
+        70: [2, 34],
144
+        71: [2, 23],
145
+        72: [2, 14],
146
+        73: [2, 30],
147
+        74: [2, 26],
148
+        75: [2, 31],
149
+        76: [2, 20],
150
+        77: [2, 28],
151
+        78: [2, 28],
152
+        79: [2, 44],
153
+        80: [2, 25],
154
+        81: [2, 36],
155
+        82: [2, 51],  # incorrect hold voltages (resample)
156
+        83: [2, 43],
157
+        84: [2, 38],
158
+        85: [2, 27],
159
+        86: [2, 43],
160
+        87: [2, 33],
161
+        88: [2, 42],
162
+        89: [3, 21], # ngz (3 15 19)
163
+        91: [2,  4], # bad samples (resample)
164
+        92: [2, 10],
165
+        93: [2, 42],
166
+        94: [2, 39],
167
+        95: [2, 19],
168
+        96: [2,  1],  # needs decreaed start us ngz: (0 22 38)
169
+        97: [2, 51],
170
+        98: [2, 30],
171
+        99: [2, 41],
172
+       100: [2, 24],
173
+       101: [2, 39],
174
+
175
+        
176
+        },
177
+
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 ],
79 180
 
80 181
       calibrateArgs: {
81 182
 
82
-        outDir: "~/temp/calib0",
83
-        outLabel: "test_1",
183
+        outDir: "~/temp/calib2",
184
+        outLabel: "test_3",
185
+
186
+        inDir: "~/temp/p_ac_3f",
84 187
         
85 188
         analysisD: {
86 189
           rmsWndMs: 300,    # length of the RMS measurment window
@@ -91,21 +194,23 @@
91 194
           durDecayPct: 40   # percent drop in RMS to indicate the end of a note
92 195
           },
93 196
     
94
-          noteOnDurMs: 1000,
95
-          noteOffDurMs: 1000,
197
+          noteOnDurMs: 500,
198
+          noteOffDurMs: 500,
96 199
     
97 200
           
98
-          pitchL: [  44, 45, 46, 47, 48, 49, 50, 51 ],                    # list of pitches
99
-          targetDbL: [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ],                  # list of target db
201
+          #pitchL:    [  31, 33, 34, 35  ],                 # list of pitches
202
+          #pitchL:    [ 80,81,82 ],  # 8
203
+          pitchL:    [ 40,41,42 ],   # 12
204
+          targetDbL: [  13 ],  # list of target db
100 205
 
101
-          minMeasDurMs: 800,             # minimum candidate note duration
206
+          minMeasDurMs: 140,             # minimum candidate note duration
102 207
           tolDbPct: 2.0,                 # tolerance as a percent of targetDb above/below used to form match db window
103 208
           maxPulseUs: 45000,             # max. allowable pulse us
104 209
           minPulseUs:  8000,             # min. allowable pulse us
105 210
           initPulseUs: 15000,            # pulseUs for first note
106 211
           minMatchN: 3,                  # at least 3 candidate notes must be within tolDbPct to move on to a new targetDb
107 212
           maxAttemptN: 30,               # give up if more than 20 candidate notes fail for a given targetDb
108
-          dbSrcLabel: 'td',              # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
213
+          dbSrcLabel: 'hm',              # source of the db measurement 'td' (time-domain) or 'hm' (harmonic)
109 214
 
110 215
           holdDutyPctD:  {
111 216
           23: [[0, 70]],

+ 1
- 1
plot_calibrate.py View File

@@ -143,7 +143,7 @@ if __name__ == "__main__":
143 143
     inDir = sys.argv[1]
144 144
     yamlFn = sys.argv[2]
145 145
     if len(sys.argv) > 3:
146
-        pitch = int(sys.argv[2])
146
+        pitch = int(sys.argv[3])
147 147
 
148 148
     keyInfoD = key_info_dictionary( yamlCfgFn=yamlFn)
149 149
     #plot_all_notes( inDir )

+ 598
- 52
plot_seq_1.py View File

@@ -1,31 +1,62 @@
1
-import os, sys
1
+import os, sys,json
2 2
 import matplotlib.pyplot as plt
3 3
 import numpy as np
4 4
 from common import parse_yaml_cfg
5 5
 import rms_analysis
6
+import elbow
7
+
8
+def fit_to_reference( pkL, refTakeId ):
9
+
10
+    us_outL  = []
11
+    db_outL  = []
12
+    dur_outL = []
13
+    tid_outL = []
14
+
15
+    dbL,usL,durMsL,takeIdL = tuple(zip(*pkL))
16
+    
17
+    us_refL,db_refL,dur_refL = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==refTakeId])
18
+
19
+    
20
+    for takeId in set(takeIdL):
21
+        us0L,db0L,dur0L = zip(*[(usL[i],dbL[i],durMsL[i]) for i in range(len(usL)) if takeIdL[i]==takeId  ])
22
+            
23
+
24
+        if takeId == refTakeId:
25
+            db_outL += db0L
26
+        else:
27
+            db1V = elbow.fit_points_to_reference(us0L,db0L,us_refL,db_refL)
28
+            db_outL += db1V.tolist()
29
+
30
+        us_outL += us0L
31
+        dur_outL+= dur0L
32
+        tid_outL+= [takeId] * len(us0L)
33
+        
34
+    return zip(db_outL,us_outL,dur_outL,tid_outL)
35
+
6 36
 
7 37
 def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
8 38
     
9 39
     inDir = os.path.join(inDir,"%i" % (midi_pitch))
10 40
 
11
-    dirL =  os.listdir(inDir)
41
+    takeDirL =  os.listdir(inDir)
12 42
 
13 43
     pkL = []
14 44
 
15
-    # for each take in this directory
16
-    for idir in dirL:
45
+    usRefL = None
46
+    dbRefL = None
17 47
 
18
-        take_number = int(idir)
48
+    # for each take in this directory
49
+    for take_number in range(len(takeDirL)):
19 50
 
20 51
         # analyze this takes audio and locate the note peaks
21
-        r = rms_analysis.rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD )
52
+        r = rms_analysis.rms_analysis_main( os.path.join(inDir,str(take_number)), midi_pitch, **analysisArgsD )
22 53
 
23 54
         # store the peaks in pkL[ (db,us) ]
24 55
         for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
25 56
             pkL.append( (db,us,stats.durMs,take_number) )
26 57
 
27
-
28
-            
58
+    pkL = fit_to_reference( pkL, 0 )
59
+    
29 60
     # sort the peaks on increasing attack pulse microseconds
30 61
     pkL = sorted( pkL, key= lambda x: x[1] )
31 62
 
@@ -37,22 +68,34 @@ def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
37 68
 
38 69
     return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
39 70
 
71
+    
72
+
73
+    
74
+
75
+    
76
+
77
+
40 78
 def select_resample_reference_indexes( noiseIdxL ):
41 79
 
42 80
     resampleIdxS = set()
43 81
 
82
+    # for each noisy sample index store that index and the index
83
+    # before and after it
44 84
     for i in noiseIdxL:
45 85
         resampleIdxS.add( i )
46
-        resampleIdxS.add( i+1 )
47
-        resampleIdxS.add( i-1 )
86
+        if i+1 < len(noiseIdxL):
87
+            resampleIdxS.add( i+1 )
88
+        if i-1 >= 0:
89
+            resampleIdxS.add( i-1 )
48 90
 
49 91
     resampleIdxL = list(resampleIdxS)
50 92
 
51 93
     # if a single sample point is left out of a region of
52
-    # contiguous sample points then include this as a resample point
94
+    # contiguous sample points then include this as a resample point also
53 95
     for i in resampleIdxL:
54 96
         if i + 1 not in resampleIdxL and i + 2 in resampleIdxL:  # BUG BUG BUG: Hardcoded constant
55
-            resampleIdxL.append(i+1)
97
+            if i+1 < len(noiseIdxL):
98
+                resampleIdxL.append(i+1)
56 99
 
57 100
     return resampleIdxL
58 101
 
@@ -99,11 +142,13 @@ def locate_resample_regions( usL, dbL, resampleIdxL ):
99 142
 
100 143
     return reUsL,reDbL
101 144
 
102
-def get_dur_skip_indexes( durMsL, dbL, takeIdL,  minDurMs, minDb ):
145
+def get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreL, minDurMs, minDb, noiseLimitPct ):
103 146
 
104 147
     firstAudibleIdx = None
105 148
     firstNonSkipIdx = None
106
-    skipIdxL = [ i for i,(ms,db) in enumerate(zip(durMsL,dbL)) if ms < minDurMs or db < minDb ]
149
+
150
+    # get the indexes of samples which do not meet the duration, db level, or noise criteria
151
+    skipIdxL = [ i for i,(ms,db,score) in enumerate(zip(durMsL,dbL,scoreL)) if ms < minDurMs or db < minDb or score > noiseLimitPct ]
107 152
 
108 153
     # if a single sample point is left out of a region of
109 154
     # contiguous skipped points then skip this point also
@@ -143,18 +188,22 @@ def get_dur_skip_indexes( durMsL, dbL, takeIdL,  minDurMs, minDb ):
143 188
 
144 189
 def get_resample_points( usL, dbL, durMsL, takeIdL, minDurMs, minDb, noiseLimitPct ):
145 190
 
146
-    skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, minDurMs, minDb )
147
-    
148
-    durL         = [ (usL[i],dbL[i]) for i in skipIdxL ]
149 191
     scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
192
+    
193
+    skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )
194
+    
195
+    skipL         = [ (usL[i],dbL[i]) for i in skipIdxL ]
150 196
     noiseIdxL    = [ i for i in range(scoreV.shape[0]) if scoreV[i] > noiseLimitPct ]
151 197
     noiseL       = [ (usL[i],dbL[i]) for i in noiseIdxL ]
152 198
     resampleIdxL = select_resample_reference_indexes( noiseIdxL )
153
-    resampleIdxL = [ i for i in resampleIdxL if i >= firstNonSkipIdx ]
199
+    
200
+    if firstNonSkipIdx is not None:
201
+        resampleIdxL = [ i for i in resampleIdxL if i >= firstNonSkipIdx ]
202
+        
154 203
     resampleL    = [ (usL[i],dbL[i]) for i in resampleIdxL   ]
155 204
     reUsL,reDbL  = locate_resample_regions( usL, dbL, resampleIdxL )
156 205
 
157
-    return reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx
206
+    return reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx
158 207
 
159 208
 def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
160 209
 
@@ -164,46 +213,75 @@ def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
164 213
     
165 214
     return reUsL
166 215
 
167
-def plot_noise_region( ax, inDir, keyMapD, midi_pitch, analysisArgsD ):
168 216
 
169
-    plotResampleFl = False
170
-    plotTakesFl    = True
171
-    usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
172 217
 
173
-    reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx  = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
218
+def plot_us_db_curves( ax, inDir, keyMapD, midi_pitch, analysisArgsD, plotResamplePointsFl=False, plotTakesFl=True, usMax=None ):
219
+
220
+    usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
221
+    reUsL, reDbL, noiseL, resampleL, skipL, firstAudibleIdx, firstNonSkipIdx  = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
174 222
 
175 223
     # plot first audible and non-skip position
176
-    ax.plot( usL[firstNonSkipIdx], dbL[firstNonSkipIdx], markersize=15, marker='+', linestyle='None', color='red')
177
-    ax.plot( usL[firstNonSkipIdx], dbL[firstAudibleIdx], markersize=15, marker='*', linestyle='None', color='red')
224
+    if False:
178 225
     
179
-    # plot the resample points
180
-    if plotResampleFl:
181
-        ax.plot( reUsL, reDbL, markersize=10, marker='x', linestyle='None', color='green')
226
+        if firstNonSkipIdx is not None:
227
+            ax.plot( usL[firstNonSkipIdx], dbL[firstNonSkipIdx], markersize=15, marker='+', linestyle='None', color='red')
228
+
229
+        if firstAudibleIdx is not None:
230
+            ax.plot( usL[firstAudibleIdx], dbL[firstAudibleIdx], markersize=15, marker='*', linestyle='None', color='red')
231
+
232
+        # plot the resample points
233
+        if plotResamplePointsFl:
234
+            ax.plot( reUsL, reDbL, markersize=13, marker='x', linestyle='None', color='green')
235
+
236
+        # plot the noisy sample positions
237
+        if noiseL:
238
+            nUsL,nDbL = zip(*noiseL)
239
+            ax.plot( nUsL, nDbL, marker='o', markersize=9, linestyle='None', color='black')
240
+
241
+        # plot the noisy sample positions and the neighbors included in the noisy region
242
+        if resampleL:
243
+            nUsL,nDbL    = zip(*resampleL)
244
+            ax.plot( nUsL, nDbL, marker='+', markersize=8, linestyle='None', color='red')
182 245
 
183
-    # plot the noisy sample positions
184
-    if noiseL:
185
-        nUsL,nDbL = zip(*noiseL)
186
-        ax.plot( nUsL, nDbL, marker='o', linestyle='None', color='black')
187
-    
188
-    # plot the noisy sample positions and the neighbors included in the noisy region
189
-    if resampleL:
190
-        nUsL,nDbL    = zip(*resampleL)
191
-        ax.plot( nUsL, nDbL, marker='*', linestyle='None', color='red')
192 246
 
247
+        
193 248
     # plot actual sample points
249
+
250
+    elbow_us  = None
251
+    elbow_db  = None
252
+    elbow_len = None
253
+
254
+    usL,dbL,takeIdL = zip(*[(us,dbL[i],takeIdL[i]) for i,us in enumerate(usL) if usMax is None or us <= usMax])
255
+    
194 256
     if plotTakesFl:
195 257
         for takeId in list(set(takeIdL)):
258
+
259
+            # get the us,db points included in this take
196 260
             xL,yL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId  ])
197
-            ax.plot(xL,yL, marker='.')
261
+            
262
+            ax.plot(xL,yL, marker='.',label=takeId)
263
+            
198 264
             for i,(x,y) in enumerate(zip(xL,yL)):
199 265
                 ax.text(x,y,str(i))
266
+
267
+
268
+            #if elbow_len is None or len(xL) > elbow_len:
269
+            if takeId+1 == len(set(takeIdL)):
270
+                elbow_us,elbow_db = elbow.find_elbow(xL,yL)
271
+                elbow_len = len(xL)
272
+                
273
+            
274
+            
200 275
     else:
201 276
         ax.plot(usL, dbL, marker='.')
202 277
 
203
-    # plot the duration skip points
204
-    if durL:
205
-        nUsL,nDbL    = zip(*durL)
206
-        ax.plot( nUsL, nDbL, marker='.', linestyle='None', color='yellow')
278
+    ax.plot([elbow_us],[elbow_db],marker='*',markersize=12,color='red',linestyle='None')
279
+        
280
+    # plot the skip points in yellow
281
+    if False:
282
+        if skipL:
283
+            nUsL,nDbL    = zip(*skipL)
284
+            ax.plot( nUsL, nDbL, marker='.', linestyle='None', color='yellow')
207 285
 
208 286
     # plot the locations where the hold duty cycle changes with vertical black lines
209 287
     for us_duty in holdDutyPctL:
@@ -213,36 +291,504 @@ def plot_noise_region( ax, inDir, keyMapD, midi_pitch, analysisArgsD ):
213 291
 
214 292
     # plot the 'minDb' reference line
215 293
     ax.axhline(analysisArgsD['resampleMinDb'] ,color='black')
216
-    
294
+
295
+    if os.path.isfile("minInterpDb.json"):
296
+        with open("minInterpDb.json","r") as f:
297
+            r = json.load(f)
298
+            if midi_pitch  in r['pitchL']:
299
+                ax.axhline( r['minDbL'][ r['pitchL'].index(midi_pitch) ], color='blue' )
300
+                ax.axhline( r['maxDbL'][ r['pitchL'].index(midi_pitch) ], color='blue' )
217 301
     
218 302
     ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
219 303
     
220
-def plot_noise_regions_main( inDir, cfg, pitchL ):
304
+def plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True, usMax=None ):
221 305
 
222 306
     analysisArgsD = cfg.analysisArgs
223 307
     keyMapD = { d['midi']:d for d in cfg.key_mapL }
224 308
     axN = len(pitchL)
225
-    fig,axL = plt.subplots(axN,1)
309
+    fig,axL = plt.subplots(axN,1,sharex=True)
226 310
     if axN == 1:
227 311
         axL = [axL]
228 312
     fig.set_size_inches(18.5, 10.5*axN)
229 313
 
230 314
     for ax,midi_pitch in zip(axL,pitchL):
231
-        plot_noise_region( ax,inDir, cfg.key_mapL, midi_pitch, analysisArgsD )
315
+        plot_us_db_curves( ax,inDir, keyMapD, midi_pitch, analysisArgsD, plotTakesFl=plotTakesFl, usMax=usMax )
316
+
317
+    if plotTakesFl:
318
+        plt.legend()
319
+        
320
+    plt.show()
321
+
322
+def plot_all_noise_curves( inDir, cfg, pitchL=None ):
323
+
324
+    pitchFolderL = os.listdir(inDir)
325
+    
326
+    if pitchL is None:
327
+        pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
328
+
329
+    fig,ax = plt.subplots()
330
+
331
+    for midi_pitch in pitchL:
332
+
333
+        print(midi_pitch)
334
+        
335
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
336
+
337
+        scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
338
+        
339
+        minDurMs      = cfg.analysisArgs['resampleMinDurMs']
340
+        minDb         = cfg.analysisArgs['resampleMinDb'],
341
+        noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
342
+
343
+        skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, scoreV.tolist(), takeIdL, minDurMs, minDb, noiseLimitPct )
344
+    
345
+
346
+        if False:
347
+            ax.plot( usL[firstAudibleIdx], scoreV[firstAudibleIdx], markersize=10, marker='*', linestyle='None', color='red')
348
+            ax.plot( usL, scoreV, label="%i"%(midi_pitch) )
349
+            ax.set_xlabel('us')
350
+
351
+        else:
352
+            xL = [ (score,db,i) for i,(score,db) in  enumerate(zip(scoreV,dbL)) ]
353
+            xL = sorted(xL, key=lambda x: x[1] )
354
+
355
+            scoreV,dbL,idxL = zip(*xL)
356
+            ax.plot( dbL[idxL[firstAudibleIdx]], scoreV[idxL[firstAudibleIdx]], markersize=10, marker='*', linestyle='None', color='red')
357
+            ax.plot( dbL, scoreV, label="%i"%(midi_pitch) )
358
+            ax.set_xlabel('db')
359
+        
360
+        ax.set_ylabel("noise db %")
361
+        
362
+    plt.legend()
363
+    plt.show()
364
+
365
+def plot_min_max_2_db( inDir, cfg, pitchL=None, takeId=2 ):
366
+
367
+    pitchFolderL = os.listdir(inDir)
368
+    
369
+    if pitchL is None:
370
+        pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
371
+
372
+
373
+    okL   = []
374
+    outPitchL = []
375
+    minDbL = []
376
+    maxDbL = []
377
+    for midi_pitch in pitchL:
378
+
379
+        print(midi_pitch)
380
+
381
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
382
+
383
+        okL.append(False)
384
+
385
+        takeId = len(set(takeIdL))-1
386
+
387
+        db_maxL = sorted(dbL)
388
+        maxDbL.append( np.mean(db_maxL[-5:]) )
389
+        
390
+
391
+        usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId  ])
392
+        
393
+        if len(set(takeIdL)) == 3:            
394
+            okL[-1] = True
395
+            
396
+        elbow_us,elbow_db = elbow.find_elbow(usL,dbL)
397
+        minDbL.append(elbow_db)
398
+        outPitchL.append(midi_pitch)
399
+
400
+
401
+
402
+    p_dL = sorted( zip(outPitchL,minDbL,maxDbL,okL), key=lambda x: x[0] )
403
+    outPitchL,minDbL,maxDbL,okL = zip(*p_dL)
404
+        
405
+    fig,ax = plt.subplots()
406
+    ax.plot(outPitchL,minDbL)
407
+    ax.plot(outPitchL,maxDbL)
408
+    
409
+    keyMapD = { d['midi']:d for d in cfg.key_mapL }
410
+    for pitch,min_db,max_db,okFl in zip(outPitchL,minDbL,maxDbL,okL):
411
+        c = 'black' if okFl else 'red'
412
+        ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
413
+        ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
414
+
415
+
416
+    plt.show()
417
+
418
+def plot_min_db_manual( inDir, cfg ):
419
+    
420
+    pitchL     = list(cfg.manualMinD.keys())
421
+    
422
+    outPitchL = []
423
+    maxDbL    = []
424
+    minDbL    = []
425
+    okL       = []
426
+    anchorMinDbL = []
427
+    anchorMaxDbL = []
428
+
429
+    for midi_pitch in pitchL:
430
+
431
+        manual_take_id    = cfg.manualMinD[midi_pitch][0]
432
+        manual_sample_idx = cfg.manualMinD[midi_pitch][1]
433
+        
434
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
435
+
436
+        okL.append(False)
437
+
438
+        takeId = len(set(takeIdL))-1
439
+
440
+        # maxDb is computed on all takes (not just the specified take)
441
+        db_maxL = sorted(dbL)
442
+        max_db = np.mean(db_maxL[-4:])
443
+        maxDbL.append( max_db )
444
+
445
+        # get the us,db values for the specified take
446
+        usL,dbL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==manual_take_id  ])
447
+
448
+        # most pitches have 3 sample takes that do not
449
+        if len(set(takeIdL)) == 3 and manual_take_id == takeId:
450
+            okL[-1] = True
451
+
452
+        # min db from the sample index manually specified in cfg
453
+        manualMinDb = dbL[ manual_sample_idx ]
454
+        
455
+        minDbL.append( manualMinDb )
456
+        outPitchL.append(midi_pitch)
457
+
458
+        
459
+        if midi_pitch in cfg.manualAnchorPitchMinDbL:
460
+            anchorMinDbL.append( manualMinDb )
461
+
462
+        if midi_pitch in cfg.manualAnchorPitchMaxDbL:
463
+            anchorMaxDbL.append( max_db )
464
+            
465
+        
466
+
467
+    # Form the complete set of min/max db levels for each pitch by interpolating the
468
+    # db values between the manually selected anchor points.
469
+    interpMinDbL = np.interp( pitchL, cfg.manualAnchorPitchMinDbL, anchorMinDbL )
470
+    interpMaxDbL = np.interp( pitchL, cfg.manualAnchorPitchMaxDbL, anchorMaxDbL )
471
+        
472
+    fig,ax = plt.subplots()
473
+
474
+    
475
+    ax.plot(outPitchL,minDbL) # plot the manually selected minDb values
476
+    ax.plot(outPitchL,maxDbL) # plot the  max db values
477
+    
478
+    # plot the interpolated minDb/maxDb values
479
+    ax.plot(pitchL,interpMinDbL)
480
+    ax.plot(pitchL,interpMaxDbL)
481
+
482
+    
483
+    keyMapD = { d['midi']:d for d in cfg.key_mapL }
484
+    for pitch,min_db,max_db,okFl in zip(outPitchL,minDbL,maxDbL,okL):
485
+        c = 'black' if okFl else 'red'
486
+        ax.text( pitch, min_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
487
+        ax.text( pitch, max_db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']), color=c)
488
+
489
+    with open("minInterpDb.json",'w') as f:
490
+        json.dump( { "pitchL":pitchL, "minDbL":list(interpMinDbL), "maxDbL":list(interpMaxDbL) }, f )
491
+
492
+            
493
+            
494
+            
495
+              
496
+
232 497
 
233 498
     plt.show()
499
+    
500
+def plot_min_max_db( inDir, cfg, pitchL=None ):
501
+
502
+    pitchFolderL = os.listdir(inDir)
503
+    
504
+    if pitchL is None:
505
+        pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
506
+
507
+
508
+    maxDbL = []
509
+    minDbL = []
510
+    for midi_pitch in pitchL:
511
+
512
+        print(midi_pitch)
513
+        
514
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
515
+
516
+        scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
234 517
 
518
+        minDurMs      = cfg.analysisArgs['resampleMinDurMs']
519
+        minDb         = cfg.analysisArgs['resampleMinDb'],
520
+        noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
235 521
 
522
+        skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )        
523
+
524
+        minDbL.append( dbL[firstAudibleIdx] )
525
+        
526
+        dbL = sorted(dbL)
527
+
528
+        x = np.mean(dbL[-3:])
529
+        x = np.max(dbL)
530
+        maxDbL.append( x )
531
+
532
+        
533
+    fig,ax = plt.subplots()
534
+
535
+    fig.set_size_inches(18.5, 10.5)
536
+
537
+    p_dL = sorted( zip(pitchL,maxDbL), key=lambda x: x[0] )
538
+    pitchL,maxDbL = zip(*p_dL)
539
+
540
+    ax.plot(pitchL,maxDbL)
541
+    ax.plot(pitchL,minDbL)
542
+    
543
+    for pitch,db in zip(pitchL,maxDbL):
544
+
545
+        keyMapD = { d['midi']:d for d in cfg.key_mapL }
546
+
547
+        ax.text( pitch, db, "%i %s %s" % (pitch, keyMapD[pitch]['type'],keyMapD[pitch]['class']))
548
+
549
+
550
+    plt.show()
551
+    
552
+def estimate_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=0.5, pitchL=None ):
553
+    
554
+    pitchFolderL = os.listdir(inDir)
555
+    
556
+    if pitchL is None:
557
+        pitchL = [ int( int(pitchFolder) ) for pitchFolder in pitchFolderL ]
558
+
559
+    mapD = {}  # pitch:{ loDb: { hiDb, us_avg, us_cls, us_std, us_min, us_max, db_avg, db_std, cnt }}
560
+               #                  where: cnt=count of valid sample points in this db range
561
+               #                         us_cls=us of closest point to center of db range
562
+               
563
+    dbS = set() # { (loDb,hiDb) }  track the set of db ranges
564
+    
565
+    for pitch in pitchL:
566
+
567
+        print(pitch)
568
+
569
+        # get the sample measurements for pitch
570
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
571
+
572
+        # calc the fit to local straight line curve fit at each point
573
+        scoreV = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
574
+        
575
+        minDurMs      = cfg.analysisArgs['resampleMinDurMs']
576
+        minDb         = cfg.analysisArgs['resampleMinDb'],
577
+        noiseLimitPct = cfg.analysisArgs['resampleNoiseLimitPct']
578
+
579
+        # get the set of samples that are not valid (too short, too quiet, too noisy)
580
+        skipIdxL, firstAudibleIdx, firstNonSkipIdx = get_dur_skip_indexes( durMsL, dbL, takeIdL, scoreV.tolist(), minDurMs, minDb, noiseLimitPct )
581
+    
582
+        mapD[ pitch ] = {}
583
+        
584
+        # get the count of  db ranges
585
+        N = int(round((maxMapDb - minMapDb) / incrMapDb)) + 1
586
+
587
+        # for each db range
588
+        for i in range(N):
589
+            
590
+            loDb = minMapDb + (i*incrMapDb)
591
+            hiDb = loDb + incrMapDb
592
+
593
+            dbS.add((loDb,hiDb))
594
+            
595
+            # get the valid (pulse,db) pairs for this range
596
+            u_dL = [(us,db) for i,(us,db) in enumerate(zip(usL,dbL)) if i not in skipIdxL and loDb<=db and db<hiDb ]
597
+
598
+            us_avg = 0
599
+            us_cls = 0
600
+            us_std = 0
601
+            us_min = 0
602
+            us_max = 0
603
+            db_avg = 0
604
+            db_std = 0
605
+            
606
+            if len(u_dL) == 0:
607
+                print("No valid samples for pitch:",pitch," db range:",loDb,hiDb)
608
+            else:
609
+                us0L,db0L = zip(*u_dL)
610
+                
611
+                if len(us0L) == 1:
612
+                    us_avg = us0L[0]
613
+                    us_cls = us_avg
614
+                    us_min = us_avg
615
+                    us_max = us_avg
616
+                    db_avg = db0L[0]
617
+
618
+                elif len(us0L) > 1:
619
+                    us_avg = np.mean(us0L)
620
+                    us_cls = us0L[ np.argmin(np.abs(np.array(db0L)-(loDb - (hiDb-loDb)/2.0 ))) ]
621
+                    us_min = np.min(us0L)
622
+                    us_max = np.max(us0L)
623
+                    us_std = np.std(us0L)
624
+                    db_avg = np.mean(db0L)
625
+                    db_std = np.std(db0L)
626
+
627
+                us_avg = int(round(us_avg))
628
+
629
+            
630
+            mapD[pitch][loDb] = { 'hiDb':hiDb, 'us_avg':us_avg, 'us_cls':us_cls, 'us_std':us_std,'us_min':us_min,'us_max':us_max, 'db_avg':db_avg, 'db_std':db_std, 'cnt':len(u_dL) }
631
+            
632
+    return mapD, list(dbS)
633
+
634
+def plot_us_to_db_map( inDir, cfg, minMapDb=16.0, maxMapDb=26.0, incrMapDb=1.0, pitchL=None ):
635
+
636
+    fig,ax = plt.subplots()
637
+    
638
+    mapD, dbRefL = estimate_us_to_db_map( inDir, cfg, minMapDb, maxMapDb, incrMapDb, pitchL )
639
+
640
+    # for each pitch
641
+    for pitch, dbD in mapD.items():
642
+
643
+        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 ]
644
+
645
+        # get the us/db lists for this pitch
646
+        usL,uscL,dbL,ussL,usnL,usxL,dbsL = zip(*u_dL)
647
+
648
+        # plot central curve and std dev's
649
+        p = ax.plot(usL,dbL, marker='.', label=str(pitch))
650
+        ax.plot(uscL,dbL, marker='x', label=str(pitch), color=p[0].get_color(), linestyle='None')
651
+        ax.plot(usL,np.array(dbL)+dbsL, color=p[0].get_color(), alpha=0.3)
652
+        ax.plot(usL,np.array(dbL)-dbsL, color=p[0].get_color(), alpha=0.3)
653
+
654
+        # plot us error bars
655
+        for db,us,uss,us_min,us_max in zip(dbL,usL,ussL,usnL,usxL):
656
+            ax.plot([us_min,us_max],[db,db], color=p[0].get_color(), alpha=0.3 )
657
+            ax.plot([us-uss,us+uss],[db,db], color=p[0].get_color(), alpha=0.3, marker='.', linestyle='None' )
658
+
659
+                    
660
+    plt.legend()
661
+    plt.show()
662
+
663
+def report_take_ids( inDir ):
664
+
665
+    pitchDirL = os.listdir(inDir)
666
+
667
+    for pitch in pitchDirL:
668
+
669
+        pitchDir = os.path.join(inDir,pitch)
670
+        
671
+        takeDirL = os.listdir(pitchDir)
672
+
673
+        if len(takeDirL) == 0:
674
+            print(pitch," directory empty")
675
+        else:
676
+            with open( os.path.join(pitchDir,'0','seq.json'), "rb") as f:
677
+                r = json.load(f)
678
+
679
+            if len(r['eventTimeL']) != 81:
680
+                print(pitch," ",len(r['eventTimeL']))
681
+
682
+            if len(takeDirL) != 3:
683
+                print("***",pitch,len(takeDirL))
684
+
685
+def cache_us_db( inDir, cfg, outFn ):
686
+
687
+    pitch_usDbD = {}
688
+    pitchDirL = os.listdir(inDir)
689
+
690
+    for pitch in pitchDirL:
691
+
692
+        pitch = int(pitch)
693
+
694
+        print(pitch)
695
+        
696
+        usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, pitch, cfg.analysisArgs['rmsAnalysisArgs'] )
697
+
698
+        pitch_usDbD[pitch] = { 'usL':usL, 'dbL':dbL, 'durMsL':durMsL, 'takeIdL':takeIdL, 'holdDutyPctL': holdDutyPctL }
699
+
700
+
701
+    with open(outFn,"w") as f:
702
+        json.dump(pitch_usDbD,f)
703
+
704
+        
705
+            
706
+def gen_vel_map( inDir, cfg, minMaxDbFn, dynLevelN, cacheFn ):
707
+
708
+    velMapD = {} # { pitch:[ us ] }
709
+    
710
+    pitchDirL = os.listdir(inDir)
711
+
712
+    with open(cacheFn,"r") as f:
713
+        pitchUsDbD = json.load(f)
714
+        
715
+
716
+    with open("minInterpDb.json","r") as f:
717
+        r = json.load(f)
718
+        minMaxDbD = { pitch:(minDb,maxDb) for pitch,minDb,maxDb in zip(r['pitchL'],r['minDbL'],r['maxDbL']) }
719
+    
720
+
721
+    pitchL = sorted( [ int(pitch) for pitch in pitchUsDbD.keys()] )
722
+    
723
+    for pitch in pitchL:
724
+        d = pitchUsDbD[str(pitch)]
725
+        
726
+        usL = d['usL']
727
+        dbL = np.array(d['dbL'])
728
+
729
+        velMapD[pitch] = []
730
+        
731
+        for i in range(dynLevelN+1):
732
+
733
+            db = minMaxDbD[pitch][0] + (i * (minMaxDbD[pitch][1] - minMaxDbD[pitch][0])/ dynLevelN)
734
+
735
+            usIdx = np.argmin( np.abs(dbL - db) )
736
+            
737
+            velMapD[pitch].append( (usL[ usIdx ],db) )
738
+        
739
+
740
+            
741
+    with open("velMapD.json","w") as f:
742
+        json.dump(velMapD,f)
743
+                
744
+    mtx = np.zeros((len(velMapD),dynLevelN+1))
745
+    print(mtx.shape)
746
+    
747
+    for i,(pitch,usDbL) in enumerate(velMapD.items()):
748
+        for j in range(len(usDbL)):
749
+            mtx[i,j] = usDbL[j][1]
750
+            
751
+    fig,ax = plt.subplots()
752
+    ax.plot(pitchL,mtx)
753
+    plt.show()
754
+        
755
+        
756
+
757
+        
758
+    
236 759
 if __name__ == "__main__":
237 760
 
238 761
     inDir = sys.argv[1]
239 762
     cfgFn = sys.argv[2]
240
-    pitch = int(sys.argv[3])
763
+    mode  = sys.argv[3]
764
+    if len(sys.argv) <= 4:
765
+        pitchL = None
766
+    else:
767
+        pitchL = [ int(sys.argv[i]) for i in range(4,len(sys.argv)) ]
241 768
 
242 769
     cfg = parse_yaml_cfg( cfgFn )
243 770
 
244
-    pitchL = [pitch]
245
-        
246
-    plot_noise_regions_main( inDir, cfg, pitchL )
771
+    if mode == 'us_db':
772
+        plot_us_db_curves_main( inDir, cfg, pitchL, plotTakesFl=True,usMax=None )
773
+    elif mode == 'noise':
774
+        plot_all_noise_curves( inDir, cfg, pitchL )
775
+    elif mode == 'min_max':
776
+        plot_min_max_db( inDir, cfg, pitchL )
777
+    elif mode == 'min_max_2':
778
+        plot_min_max_2_db( inDir, cfg, pitchL )
779
+    elif mode == 'us_db_map':
780
+        plot_us_to_db_map( inDir, cfg, pitchL=pitchL )
781
+    elif mode == 'audacity':
782
+        rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )
783
+    elif mode == 'rpt_take_ids':
784
+        report_take_ids( inDir )
785
+    elif mode == 'manual_db':
786
+        plot_min_db_manual( inDir, cfg )
787
+    elif mode == 'gen_vel_map':
788
+        gen_vel_map( inDir, cfg, "minInterpDb.json", 9, "cache_us_db.json" )
789
+    elif mode == 'cache_us_db':
790
+        cache_us_db( inDir, cfg, "cache_us_db.json")
791
+    else:
792
+        print("Unknown mode:",mode)
793
+
247 794
     
248
-    #rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )

+ 22
- 15
plot_us_db_range.ipynb
File diff suppressed because it is too large
View File


+ 56
- 10
rms_analysis.py View File

@@ -30,8 +30,8 @@ def rms_to_db( xV, rms_srate, dbLinRef ):
30 30
     #dbWndN = int(round(refWndMs * rms_srate / 1000.0))
31 31
     #dbRef = ref = np.mean(xV[0:dbWndN])
32 32
 
33
-    #print("DB REF:",dbLinRef)
34
-    rmsDbV = 20.0 * np.log10( xV / dbLinRef )
33
+    #print("DB REF:",dbLinRef, min(xV), np.argmin(xV))
34
+    rmsDbV = 20.0 * np.log10( (xV+np.nextafter(0,1)) / dbLinRef )
35 35
 
36 36
     return rmsDbV
37 37
 
@@ -41,7 +41,9 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef  ):
41 41
     hopSmpN = int(round( hopMs    * srate / 1000.0))
42 42
 
43 43
     xN   = xV.shape[0]
44
+    
44 45
     yN   = int(((xN - wndSmpN) / hopSmpN) + 1)
46
+
45 47
     assert( yN > 0)
46 48
     yV   = np.zeros( (yN, ) )
47 49
 
@@ -52,7 +54,7 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef  ):
52 54
     while i < xN and j < yN:
53 55
 
54 56
         if i == 0:
55
-            yV[j] = np.sqrt(xV[0]*xV[0])
57
+            yV[j] = np.sqrt(xV[0]*xV[0]) 
56 58
         elif i < wndSmpN:
57 59
             yV[j] = np.sqrt( np.mean( xV[0:i] * xV[0:i] ) )
58 60
         else:
@@ -62,7 +64,7 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef  ):
62 64
         j += 1
63 65
 
64 66
     rms_srate = srate / hopSmpN
65
-    return rms_to_db( yV, rms_srate, dbLinRef ), rms_srate
67
+    return rms_to_db( yV[0:j], rms_srate, dbLinRef ), rms_srate
66 68
 
67 69
 
68 70
 def audio_stft_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, spectrumIdx ):
@@ -219,12 +221,19 @@ def note_stats( r, decay_pct=50.0, extraDurSearchMs=500 ):
219 221
         if qualityCoeff > qmax:
220 222
             qmax = qualityCoeff
221 223
 
222
-        durAvgDb = (np.mean(r.rmsDbV[bi:ei]) + np.mean(r.tdRmsDbV[bi:ei]))/2.0
224
+        if ei-bi == 0:
225
+            tdRmsDb_v = 0.0 if bi >= len(r.tdRmsDbV) else np.mean(r.tdRmsDbV[bi])
226
+            hmRmsDb_v = 0.0 if bi >= len(r.rmsDbV)   else np.mean(r.rmsDbV[bi])            
227
+            durAvgDb = (hmRmsDb_v + tdRmsDb_v)/2.0
228
+        else:
229
+            tdRmsDb_u = 0.0 if ei >= len(r.tdRmsDbV) else np.mean(r.tdRmsDbV[bi:ei])
230
+            hmRmsDb_u = 0.0 if ei >= len(r.rmsDbV)   else np.mean(r.rmsDbV[bi:ei])
231
+            durAvgDb = (hmRmsDb_u + tdRmsDb_u)/2.0
223 232
 
224 233
         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 }))
225 234
 
226 235
     for i,r in enumerate(statsL):
227
-        statsL[i].quality /= qmax
236
+        statsL[i].quality = 0 if qmax <= 0 else statsL[i].quality / qmax
228 237
                            
229 238
     
230 239
     return statsL
@@ -284,6 +293,42 @@ def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300
284 293
     
285 294
     return { "td":tdD, "hm":hmD }
286 295
 
296
+def rms_analyze_one_rt_note_wrap( audioDev, annBegMs, annEndMs, midi_pitch, noteOffDurMs, rmsAnalysisD ):
297
+
298
+    resD = None 
299
+    buf_result = audioDev.linear_buffer()
300
+
301
+    if buf_result:
302
+
303
+        sigV = buf_result.value
304
+
305
+        # get the annotated begin and end of the note as sample indexes into sigV
306
+        bi = int(round(annBegMs * audioDev.srate / 1000))
307
+        ei = int(round(annEndMs * audioDev.srate / 1000))
308
+
309
+        # calculate half the length of the note-off duration in samples
310
+        noteOffSmp_o_2 = int(round( (noteOffDurMs/2)  * audioDev.srate / 1000))
311
+
312
+        # widen the note analysis space noteOffSmp_o_2 samples pre/post the annotated begin/end of the note
313
+        bi = max(0,bi - noteOffSmp_o_2)
314
+        ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1)
315
+
316
+
317
+        ar = types.SimpleNamespace(**rmsAnalysisD)
318
+
319
+        # shift the annotatd begin/end of the note to be relative to  index bi
320
+        begMs = noteOffSmp_o_2 * 1000 / audioDev.srate
321
+        endMs = begMs + (annEndMs - annBegMs)
322
+
323
+        #print("MEAS:",begMs,endMs,bi,ei,sigV.shape,audioDev.is_recording_enabled(),ar)
324
+
325
+
326
+        # analyze the note
327
+        resD  = rms_analyze_one_rt_note( sigV[bi:ei], audioDev.srate, begMs, endMs, midi_pitch, rmsWndMs=ar.rmsWndMs, rmsHopMs=ar.rmsHopMs, dbLinRef=ar.dbLinRef, harmCandN=ar.harmCandN, harmN=ar.harmN, durDecayPct=ar.durDecayPct )
328
+
329
+        #print( "hm:%4.1f %4i  td:%4.1f %4i" %  (resD['hm']['db'], resD['hm']['durMs'], resD['td']['db'], resD['td']['durMs']))
330
+
331
+    return resD
287 332
 
288 333
 def calibrate_rms( sigV, srate, beg_ms, end_ms ):
289 334
 
@@ -368,7 +413,7 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.
368 413
     tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate,  r['eventTimeL'])
369 414
     
370 415
     rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN  )
371
-    
416
+
372 417
     pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'] )    
373 418
 
374 419
     holdDutyPctL = None
@@ -393,7 +438,6 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.
393 438
         'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ],
394 439
         'pkUsL':r['pulseUsL'] })
395 440
 
396
-
397 441
     statsL = note_stats(r,durDecayPct)
398 442
 
399 443
     setattr(r,"statsL",   statsL )
@@ -492,7 +536,7 @@ def samples_to_linear_residual( usL, dbL, pointsPerLine=5 ):
492 536
     assert( len(scoreL) == len(usL) )
493 537
     return np.array(scoreL)
494 538
         
495
-def write_audacity_label_files( inDir, analysisArgsD ):
539
+def write_audacity_label_files( inDir, analysisArgsD, reverseFl=True ):
496 540
 
497 541
     pitchDirL = os.listdir(inDir)
498 542
 
@@ -520,7 +564,9 @@ def write_audacity_label_files( inDir, analysisArgsD ):
520 564
                 
521 565
                 for i,s in enumerate(r.statsL):
522 566
 
523
-                    label = "%i %4.1f %6.1f" % (i, s.pkDb, s.durMs )
567
+                    noteIndex = len(r.statsL)-(i+1) if reverseFl else i
568
+                    
569
+                    label = "%i %4.1f %6.1f" % (noteIndex, s.pkDb, s.durMs )
524 570
                     
525 571
                     f.write("%f\t%f\t%s\n" % ( s.begSmpSec, s.endSmpSec, label ))
526 572
 

+ 162
- 0
velMapD.h View File

@@ -0,0 +1,162 @@
1
+{
2
+{ 23, { 12800, 12950, 13175, 13500, 13750, 14750, 15375, 17500, 23000, 37000, } },
3
+{ 24, { 12425, 12800, 13175, 14225, 14750, 15500, 17500, 22500, 32000, 39000, } },
4
+{ 25, { 14150, 14375, 14975, 14625, 15500, 16500, 20000, 28500, 40000, 40000, } },
5
+{ 26, { 13000, 13175, 13500, 13700, 13925, 14250, 15000, 16250, 19000, 26500, } },
6
+{ 27, { 13625, 13925, 14075, 14250, 14500, 14875, 15375, 16500, 18750, 25000, } },
7
+{ 28, { 12625, 13750, 13775, 14225, 14500, 16500, 18000, 20000, 25500, 34000, } },
8
+{ 29, { 12125, 12725, 13000, 12950, 14150, 15500, 16250, 17750, 21500, 28000, } },
9
+{ 30, { 13175, 13325, 13550, 14450, 14875, 15500, 16250, 17750, 21500, 27000, } },
10
+{ 31, { 13925, 14075, 14450, 14625, 15500, 16250, 16750, 17750, 19500, 23500, } },
11
+{ 32, { 13250, 14150, 14975, 14750, 15250, 16000, 17500, 21000, 27000, 38000, } },
12
+{ 33, { 11825, 13025, 14075, 14825, 14375, 14875, 16250, 17500, 22000, 28000, } },
13
+{ 34, { 13025, 13375, 13325, 13775, 14375, 14500, 15250, 18000, 22000, 27000, } },
14
+{ 35, { 11375, 12250, 12350, 12725, 14225, 13750, 15375, 17000, 20500, 25000, } },
15
+{ 36, { 11750, 13875, 14125, 14225, 14675, 14750, 16500, 18500, 22500, 32000, } },
16
+{ 37, { 12425, 12575, 13000, 13025, 13375, 15000, 16000, 18750, 25500, 35000, } },
17
+{ 38, { 13750, 13875, 14075, 14600, 14750, 15500, 17750, 21500, 27500, 37000, } },
18
+{ 39, { 11000, 12500, 12950, 13700, 14875, 15500, 16250, 20000, 26500, 37000, } },
19
+{ 40, { 11525, 11750, 12125, 12500, 12875, 13500, 14625, 18250, 23500, 29000, } },
20
+{ 41, { 11675, 11750, 12500, 13000, 13925, 15250, 17000, 20000, 26500, 36000, } },
21
+{ 42, { 11875, 12000, 11975, 12050, 12275, 13375, 15000, 17250, 22000, 29000, } },
22
+{ 43, { 11500, 11625, 11750, 11750, 12625, 12250, 13625, 16750, 19500, 25500, } },
23
+{ 44, { 12425, 12500, 12750, 12650, 13000, 14000, 15250, 16500, 20000, 27000, } },
24
+{ 45, { 11250, 11600, 11875, 12000, 12250, 13100, 14750, 15500, 18250, 25500, } },
25
+{ 46, { 11450, 11525, 11600, 11625, 11875, 12250, 14000, 15750, 17750, 21500, } },
26
+{ 47, { 11900, 11975, 12125, 12375, 13125, 14375, 15750, 18750, 22500, 28500, } },
27
+{ 48, { 11750, 13100, 13325, 13625, 14300, 14975, 15750, 19000, 24000, 30000, } },
28
+{ 49, { 11975, 12050, 12500, 12750, 13125, 14000, 17000, 20000, 25500, 40000, } },
29
+{ 50, { 11625, 11525, 11750, 11825, 12125, 12375, 14750, 16250, 19000, 25500, } },
30
+{ 51, { 12050, 12125, 12125, 12275, 12350, 12500, 12875, 16250, 18500, 22500, } },
31
+{ 52, { 12950, 13025, 13125, 13175, 13250, 13500, 13875, 15750, 18000, 22000, } },
32
+{ 53, { 10600, 10250, 10350, 10450, 10900, 11375, 13025, 14750, 18250, 26500, } },
33
+{ 54, { 12650, 12625, 12725, 12800, 13000, 13625, 16250, 18500, 23000, 32000, } },
34
+{ 55, { 11875, 12125, 12250, 12425, 12875, 13175, 13750, 17250, 20000, 26000, } },
35
+{ 56, { 11625, 11750, 12000, 12200, 12500, 13125, 14375, 17000, 20500, 26500, } },
36
+{ 57, { 11625, 11750, 12125, 12275, 12750, 14625, 16750, 20000, 25500, 39000, } },
37
+{ 58, { 12000, 12500, 12750, 12875, 13100, 13375, 15000, 17750, 21000, 28000, } },
38
+{ 59, { 11625, 11525, 12050, 13375, 13625, 14150, 16500, 21000, 24500, 30000, } },
39
+{ 60, { 12250, 12250, 12375, 12350, 13000, 13500, 16000, 17750, 22000, 29000, } },
40
+{ 61, { 11375, 11500, 11625, 11750, 12000, 12200, 12725, 13625, 17500, 21000, } },
41
+{ 62, { 11600, 11675, 11825, 12125, 12650, 13375, 14375, 18500, 24500, 32000, } },
42
+{ 63, { 12125, 12200, 12350, 12500, 13025, 13625, 16250, 18750, 24500, 36000, } },
43
+{ 64, { 10550, 10650, 10850, 11250, 11875, 12250, 14000, 16250, 19500, 26500, } },
44
+{ 65, { 12750, 12800, 12875, 13175, 13250, 14625, 14975, 17500, 20500, 26000, } },
45
+{ 66, { 10750, 11000, 11250, 11500, 12000, 12875, 15375, 17000, 20500, 28500, } },
46
+{ 67, { 10950, 11125, 11250, 11500, 11875, 13875, 15750, 17750, 23000, 37000, } },
47
+{ 68, { 10150, 10300, 10550, 10800, 11125, 11875, 13000, 16000, 19000, 25000, } },
48
+{ 69, { 11750, 11875, 12375, 12500, 12750, 13500, 16250, 18250, 23500, 31000, } },
49
+{ 70, { 10700, 10850, 10950, 11125, 11625, 13875, 14500, 15750, 18750, 24500, } },
50
+{ 71, { 10200, 10700, 11000, 11250, 11625, 14000, 14875, 16250, 22000, 27000, } },
51
+{ 72, {  9800, 10100, 10400, 10550, 11000, 11625, 13000, 15500, 17750, 23000, } },
52
+{ 73, { 10750, 10900, 11125, 11375, 11625, 12750, 14750, 15500, 18500, 23000, } },
53
+{ 74, { 10300, 10450, 10600, 10850, 11250, 12000, 14250, 15000, 17500, 21000, } },
54
+{ 75, { 10600, 10750, 10900, 11125, 12500, 14500, 14750, 15000, 21000, 31000, } },
55
+{ 76, { 10200, 11625, 12375, 12875, 13500, 15750, 19000, 22500, 27500, 39000, } },
56
+{ 77, { 10500, 10700, 11125, 11375, 11750, 14000, 14875, 16500, 20500, 27000, } },
57
+{ 78, { 10450, 10800, 11000, 11625, 12000, 13125, 15500, 18250, 22000, 34000, } },
58
+{ 79, { 12250, 13500, 14125, 14750, 16250, 17500, 19000, 24500, 31000, 40000, } },
59
+{ 80, { 10400, 10450, 10750, 11125, 12125, 13375, 14750, 17750, 23000, 39000, } },
60
+{ 81, { 10800, 10950, 11125, 11375, 12625, 13875, 14875, 16000, 19000, 23500, } },
61
+{ 82, { 12000, 12375, 13750, 13750, 12625, 14000, 17000, 19000, 21000, 24500, } },
62
+{ 83, { 12250, 12500, 13625, 13875, 14375, 16500, 17750, 20500, 25000, 35000, } },
63
+{ 84, { 11500, 12000, 12250, 12500, 13125, 14250, 15375, 16750, 19500, 25500, } },
64
+{ 85, { 10400, 10500, 10600, 11250, 12250, 13375, 15000, 16750, 20000, 26000, } },
65
+{ 86, { 11500, 11750, 11875, 12000, 12250, 12500, 13500, 15000, 20500, 21000, } },
66
+{ 87, { 10650, 11500, 13125, 13375, 13750, 14500, 16500, 18000, 20000, 24000, } },
67
+{ 88, { 11375, 11375, 11500, 12375, 12000, 13375, 14500, 16500, 19000, 23000, } },
68
+{ 89, {  9200, 10900, 11500, 12125, 22000, 12875, 14125, 16000, 19000, 26500, } },
69
+{ 91, {  9450,  9950, 10000, 10150, 10600, 11250, 12125, 13875, 15250, 19000, } },
70
+{ 92, {  9050,  9500,  9600, 10100, 10900, 11875, 13000, 16000, 20500, 31000, } },
71
+{ 93, { 11250, 11375, 12000, 12375, 12875, 13625, 14250, 17500, 21500, 39000, } },
72
+{ 94, { 11125, 11375, 11750, 13500, 14000, 14875, 15750, 17750, 22000, 25500, } },
73
+{ 95, { 10200, 10350, 11500, 12250, 12500, 13125, 13875, 15250, 19000, 21000, } },
74
+{ 96, {  9050,  9550, 10100, 13875, 13000, 14000, 18500, 22000, 27000, 39000, } },
75
+{ 97, { 11000, 12500, 13250, 13000, 13750, 15750, 15000, 18000, 19000, 22500, } },
76
+{ 98, { 10400, 10850, 12125, 12125, 13250, 13875, 16000, 18750, 26500, 37000, } },
77
+{ 99, { 11000, 12625, 13125, 14000, 15500, 16750, 19000, 21500, 25000, 36000, } },
78
+{ 100, {  9650, 10450, 11500, 12375, 12500, 12875, 13500, 15500, 17500, 21500, } },
79
+{ 101, { 10950, 11250, 11500, 11875, 12250, 12875, 13500, 14375, 22500, 39000, } },
80
+}
81
+
82
+{
83
+23, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
84
+24, {{ 0, 75 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
85
+25, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
86
+26, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
87
+27, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
88
+28, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
89
+29, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
90
+30, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
91
+31, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
92
+32, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
93
+33, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
94
+34, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
95
+35, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
96
+36, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
97
+37, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
98
+38, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
99
+39, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
100
+40, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
101
+41, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
102
+42, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
103
+43, {{ 0, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
104
+44, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
105
+45, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
106
+46, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
107
+47, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
108
+48, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
109
+49, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
110
+50, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
111
+51, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
112
+52, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
113
+53, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
114
+54, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
115
+55, {{ 0, 50 }, { 22000, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
116
+56, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
117
+57, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
118
+58, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
119
+59, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
120
+60, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
121
+61, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
122
+62, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
123
+63, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
124
+64, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
125
+65, {{ 0, 50 }, { 17000, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
126
+66, {{ 0, 53 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
127
+67, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
128
+68, {{ 0, 53 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
129
+69, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
130
+70, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
131
+71, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
132
+72, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
133
+73, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
134
+74, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
135
+75, {{ 0, 55 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
136
+76, {{ 0, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
137
+77, {{ 0, 50 }, { 15000, 60 }, { 19000, 70 }, { -1, -1 }, { -1, -1 }, }
138
+78, {{ 0, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
139
+79, {{ 0, 50 }, { 15000, 60 }, { 19000, 70 }, { -1, -1 }, { -1, -1 }, }
140
+80, {{ 0, 45 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
141
+81, {{ 0, 50 }, { 15000, 70 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
142
+82, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
143
+83, {{ 0, 50 }, { 15000, 65 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
144
+84, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
145
+85, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
146
+86, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
147
+87, {{ 0, 50 }, { 14000, 60 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
148
+88, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
149
+89, {{ 0, 50 }, { 12500, 60 }, { 14000, 65 }, { 17000, 70 }, { -1, -1 }, }
150
+91, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
151
+92, {{ 0, 40 }, { 14000, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
152
+93, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
153
+94, {{ 0, 40 }, { 14000, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
154
+95, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
155
+96, {{ 0, 40 }, { 12500, 50 }, { 14000, 60 }, { 17000, 65 }, { -1, -1 }, }
156
+97, {{ 0, 40 }, { 14000, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
157
+98, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
158
+99, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
159
+100, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
160
+101, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
161
+106, {{ 0, 50 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }, }
162
+}

+ 1
- 0
velMapD.json
File diff suppressed because it is too large
View File


+ 53
- 0
velTableToDataStruct.py View File

@@ -0,0 +1,53 @@
1
+import json
2
+from common import parse_yaml_cfg
3
+
4
+ymlFn = 'p_ac.yml'
5
+ifn = 'velMapD.json'
6
+ofn = 'velMapD.h'
7
+
8
+with open(ofn,"wt") as f1:
9
+
10
+    with open(ifn,"r") as f:
11
+        d = json.load(f)
12
+
13
+        f1.write("{\n");
14
+        for key,velL in d.items():
15
+            f1.write("{ ")
16
+            f1.write( str(key) + ", { " )
17
+            for us,x in velL:
18
+                f1.write("%5i, " % (us))
19
+            f1.write("} },\n")
20
+        f1.write("}\n\n");
21
+            
22
+
23
+    
24
+    cfg = parse_yaml_cfg(ymlFn)
25
+
26
+    d = cfg.calibrateArgs['holdDutyPctD']
27
+
28
+    n = 0
29
+    for key,dutyL in d.items():
30
+        n = max(n, len(dutyL))
31
+
32
+    f1.write("{\n")
33
+        
34
+    for key,dutyL in d.items():
35
+        f1.write( str(key)+", {")
36
+        
37
+        for i,(us,duty) in enumerate(dutyL):
38
+            f1.write("{ %i, %i }, " % (us,duty))
39
+
40
+        for j in range(i,n):
41
+            f1.write("{ -1, -1 }, ")
42
+            
43
+        f1.write("},\n");
44
+
45
+            
46
+                    
47
+    f1.write("}\n");
48
+        
49
+        
50
+    
51
+
52
+    
53
+        

Loading…
Cancel
Save