Quellcode durchsuchen

Many changes and additions.

First working version of calibrate.py
Replaced analysis parameter dbRefWndMs with dbLinRef
rms_analysis.py : Added samples_to_linear_residual()
plot_seq_1.py : initial commit.
master
kpl vor 4 Jahren
Ursprung
Commit
ca9580cd50
12 geänderte Dateien mit 649 neuen und 126 gelöschten Zeilen
  1. 7
    0
      AudioDevice.py
  2. 81
    14
      calibrate.py
  3. 37
    42
      p_ac.py
  4. 13
    8
      p_ac.yml
  5. 8
    5
      plot_calibrate.ipynb
  6. 7
    4
      plot_calibrate.py
  7. 11
    10
      plot_note_analysis.py
  8. 45
    7
      plot_seq.py
  9. 248
    0
      plot_seq_1.py
  10. 76
    0
      plot_us_db_range.ipynb
  11. 114
    34
      rms_analysis.py
  12. 2
    2
      rt_note_analysis.py

+ 7
- 0
AudioDevice.py Datei anzeigen

@@ -126,6 +126,13 @@ class AudioDevice(object):
126 126
 
127 127
         return res
128 128
 
129
+    def is_recording_enabled( self ):
130
+        
131
+        if self.inStream is None:
132
+            return False
133
+
134
+        return self.inStream.active == True
135
+        
129 136
     def record_enable( self, enableFl ):
130 137
 
131 138
         # if the input stream has not already been configured

+ 81
- 14
calibrate.py Datei anzeigen

@@ -1,6 +1,6 @@
1 1
 import os,types,wave,json,array
2 2
 import numpy as np
3
-from rms_analysis import rms_analyze_one_note
3
+from rms_analysis import rms_analyze_one_rt_note
4 4
 
5 5
 class Calibrate:
6 6
     def __init__( self, cfg, audio, midi, api ):
@@ -65,9 +65,9 @@ class Calibrate:
65 65
         if self.midi is not None:
66 66
             self.midi.send_all_notes_off()
67 67
 
68
-        if not self.playOnlyFl:
69
-            self.audio.record_enable(False)
68
+        self.audio.record_enable(False)
70 69
 
70
+        if not self.playOnlyFl:
71 71
             self._save_results()
72 72
 
73 73
     def play(self,ms):
@@ -75,11 +75,15 @@ class Calibrate:
75 75
         if self.measD is None or len(self.measD) == 0:
76 76
             print("Nothing to play.")
77 77
         else:
78
+            self.startMs           = ms
78 79
             self.state             = 'started'
79 80
             self.playOnlyFl        = True
80 81
             self.nextStateChangeMs = ms + 500
81 82
             self.curPitchIdx       = -1
82 83
             self.curTargetDbIdx    = 0
84
+            
85
+            self.audio.record_enable(True)
86
+
83 87
             self._do_play_update()
84 88
         
85 89
     def tick(self,ms):
@@ -102,6 +106,7 @@ class Calibrate:
102 106
             elif self.state == 'note_off':
103 107
                 if self.playOnlyFl:
104 108
                     if not self._do_play_update():
109
+                        self.stop(ms)
105 110
                         self.state = 'stopped'
106 111
                 else:
107 112
                     if self._do_analysis(ms):
@@ -113,10 +118,29 @@ class Calibrate:
113 118
                 # if the state was not changed to 'stopped'
114 119
                 if self.state == 'note_off':
115 120
                     self.state = 'started'
116
-                    
121
+                                    
122
+
123
+    def _calc_play_pulse_us( self, pitch, targetDb ):
124
+
125
+        pulseDbL = []
126
+        for d in self.measD[ pitch ]:
127
+            if d['targetDb'] == targetDb and d['matchFl']==True:
128
+                pulseDbL.append( ( d['pulse_us'], d[self.cfg.dbSrcLabel]['db']) )
129
+
130
+        if len(pulseDbL) == 0:
131
+            return -1
132
+        
133
+        pulseL,dbL = zip(*pulseDbL)
134
+
135
+        # TODO: make a weighted average based on db error
136
+        
137
+        return np.mean(pulseL)
117 138
                 
118 139
     def _do_play_update( self ):
119 140
 
141
+        if self.curPitchIdx >= 0:
142
+            self._meas_note( self.cfg.pitchL[self.curPitchIdx], self.curPulseUs )
143
+        
120 144
         self.curPitchIdx +=1
121 145
         if self.curPitchIdx >= len(self.cfg.pitchL):
122 146
             self.curPitchIdx = 0
@@ -124,13 +148,10 @@ class Calibrate:
124 148
             if self.curTargetDbIdx >= len(self.cfg.targetDbL):
125 149
                 return False
126 150
 
127
-        pitch = self.cfg.pitchL[ self.curPitchIdx ]
151
+        pitch    = self.cfg.pitchL[ self.curPitchIdx ]
128 152
         targetDb = self.cfg.targetDbL[ self.curTargetDbIdx ]
129
-        self.curPulseUs = -1
130
-        for d in self.measD[ pitch ]:
131
-            if d['targetDb'] == targetDb and d['matchFl']==True:
132
-                self.curPulseUs = d['pulse_us']
133
-                break
153
+        self.curPulseUs  = self._calc_play_pulse_us( pitch, targetDb )
154
+        self.curTargetDb = targetDb
134 155
 
135 156
         if self.curPulseUs == -1:
136 157
             print("Pitch:%i TargetDb:%f not found." % (pitch,targetDb))
@@ -195,10 +216,47 @@ class Calibrate:
195 216
         #print("note-off: ",self.cfg.pitchL[ self.curPitchIdx])
196 217
 
197 218
 
219
+    def _proportional_step( self, targetDb, dbL, pulseL ):
220
+
221
+        curPulse,curDb = self.pulseDbL[-1]
222
+
223
+        # get the point closest to the target db
224
+        i = np.argmin( np.array(dbL) - targetDb )
225
+
226
+        # find the percentage difference to the target - based on the closest point
227
+        pd = abs(curDb-targetDb) / abs(curDb - dbL[i])
228
+
229
+        # 
230
+        delta_pulse = pd * abs(curPulse - pulseL[i])
231
+        print("prop:",pd,"delta_pulse:",delta_pulse)
232
+
233
+        return int(round(curPulse + np.sign(targetDb - curDb) * delta_pulse))
234
+
235
+    def _step( self, targetDb, dbL, pulseL ):
236
+
237
+        pulse0,db0 = self.pulseDbL[-2]
238
+        pulse1,db1 = self.pulseDbL[-1] 
239
+
240
+        # microseconds per decibel for the last two points
241
+        us_per_db = abs(pulse0-pulse1) / abs(db0-db1)
242
+
243
+        if us_per_db == 0:
244
+            us_per_db = 10  # ************************************** CONSTANT ***********************
245
+
246
+        # calcuate the decibels we need to move from the last point
247
+        error_db = targetDb - db1
248
+
249
+        print("us_per_db:",us_per_db," error db:", error_db )
250
+
251
+        return pulse1 + us_per_db * error_db
252
+
253
+        
254
+        
255
+    
198 256
     def _calc_next_pulse_us( self, targetDb ):
199 257
 
200 258
         # sort pulseDb ascending on db
201
-        self.pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
259
+        #self.pulseDbL = sorted( self.pulseDbL, key=lambda x: x[1] )
202 260
 
203 261
         
204 262
         pulseL,dbL = zip(*self.pulseDbL)
@@ -223,6 +281,10 @@ class Calibrate:
223 281
             self.deltaDnMult = 1
224 282
             pu =  np.interp([targetDb],dbL,pulseL)
225 283
 
284
+            if int(pu) in pulseL:
285
+                pu = self._step(targetDb, dbL, pulseL )
286
+            
287
+
226 288
         return max(min(pu,self.cfg.maxPulseUs),self.cfg.minPulseUs)
227 289
     
228 290
     def _do_analysis(self,ms):
@@ -309,16 +371,18 @@ class Calibrate:
309 371
 
310 372
             sigV = buf_result.value
311 373
 
374
+            
375
+
312 376
             # get the annotated begin and end of the note as sample indexes into sigV
313 377
             bi = int(round(annD['beg_ms'] * self.audio.srate / 1000))
314 378
             ei = int(round(annD['end_ms'] * self.audio.srate / 1000))
315 379
 
316 380
             # calculate half the length of the note-off duration in samples
317
-            noteOffSmp_o_2 = int(round(self.cfg.noteOffDurMs/2  * self.audio.srate / 1000))
381
+            noteOffSmp_o_2 = int(round( (self.cfg.noteOffDurMs/2)  * self.audio.srate / 1000))
318 382
 
319 383
             # widen the note analysis space noteOffSmp_o_2 samples pre/post the annotated begin/end of the note
320 384
             bi = max(0,bi - noteOffSmp_o_2)
321
-            ei = min(noteOffSmp_o_2,sigV.shape[0]-1)
385
+            ei = min(ei+noteOffSmp_o_2,sigV.shape[0]-1)
322 386
 
323 387
             
324 388
             ar = types.SimpleNamespace(**self.cfg.analysisD)
@@ -327,8 +391,11 @@ class Calibrate:
327 391
             begMs = noteOffSmp_o_2 * 1000 / self.audio.srate
328 392
             endMs = begMs + (annD['end_ms'] - annD['beg_ms'])
329 393
 
394
+            #print("MEAS:",begMs,endMs,bi,ei,sigV.shape,self.audio.is_recording_enabled(),ar)
395
+
396
+
330 397
             # analyze the note
331
-            resD  = rms_analyze_rt_one_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 )
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 )
332 399
 
333 400
             resD["pulse_us"]   = pulse_us 
334 401
             resD["midi_pitch"] = midi_pitch

+ 37
- 42
p_ac.py Datei anzeigen

@@ -10,20 +10,21 @@ 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 14
 from plot_seq     import form_final_pulse_list
14 15
 from rt_note_analysis import RT_Analyzer
15 16
 from keyboard         import Keyboard
16 17
 from calibrate        import Calibrate
17 18
 
18 19
 class AttackPulseSeq:
19
-    """ Sequence a fixed chord over a list of attack pulse lengths."""
20
+    """ Sequence a fixed pitch over a list of attack pulse lengths."""
20 21
     
21 22
     def __init__(self, cfg, audio, api, noteDurMs=1000, pauseDurMs=1000 ):
22 23
         self.cfg         = cfg
23 24
         self.audio       = audio
24 25
         self.api         = api
25 26
         self.outDir      = None           # directory to write audio file and results
26
-        self.pitchL      = None           # chord to play
27
+        self.pitch       = None           # pitch to paly
27 28
         self.pulseUsL    = []             # one onset pulse length in microseconds per sequence element
28 29
         self.noteDurMs   = noteDurMs      # duration of each chord in milliseconds
29 30
         self.pauseDurMs  = pauseDurMs     # duration between end of previous note and start of next
@@ -38,9 +39,9 @@ class AttackPulseSeq:
38 39
         self.playOnlyFl           = False
39 40
         self.rtAnalyzer           = RT_Analyzer()
40 41
 
41
-    def start( self, ms, outDir, pitchL, pulseUsL, holdDutyPctL, playOnlyFl=False ):
42
+    def start( self, ms, outDir, pitch, pulseUsL, holdDutyPctL, playOnlyFl=False ):
42 43
         self.outDir     = outDir         # directory to write audio file and results
43
-        self.pitchL     = pitchL         # chord to play
44
+        self.pitch     = pitch           # note to play
44 45
         self.pulseUsL   = pulseUsL       # one onset pulse length in microseconds per sequence element
45 46
         self.holdDutyPctL = holdDutyPctL
46 47
         self.pulse_idx  = 0
@@ -51,9 +52,6 @@ class AttackPulseSeq:
51 52
         self.beginMs    = ms
52 53
         self.playOnlyFl = playOnlyFl
53 54
 
54
-        #for pitch in pitchL:
55
-        #    self.api.set_pwm_duty( pitch, self.holdDutyPct )
56
-        #    print("set PWM:%i"%(self.holdDutyPct))
57 55
 
58 56
         # kpl if not playOnlyFl:
59 57
         self.audio.record_enable(True)   # start recording audio
@@ -121,11 +119,10 @@ class AttackPulseSeq:
121 119
         self.next_ms = ms + self.noteDurMs
122 120
         self.state = 'note_off'
123 121
 
124
-        for pitch in self.pitchL:
125
-            pulse_usec = int(self.pulseUsL[ self.pulse_idx ])
126
-            self._set_duty_cycle( pitch, pulse_usec )
127
-            self.api.note_on_us( pitch, pulse_usec )
128
-            print("note-on:",pitch, self.pulse_idx, pulse_usec)
122
+        pulse_usec = int(self.pulseUsL[ self.pulse_idx ])
123
+        self._set_duty_cycle( self.pitch, pulse_usec )
124
+        self.api.note_on_us( self.pitch, pulse_usec )
125
+        print("note-on:",self.pitch, self.pulse_idx, pulse_usec)
129 126
 
130 127
     def _note_off( self, ms ):
131 128
         self.eventTimeL[ self.pulse_idx ][1] = self.audio.buffer_sample_ms().value
@@ -135,15 +132,14 @@ class AttackPulseSeq:
135 132
         if self.playOnlyFl:
136 133
             begTimeMs = self.eventTimeL[ self.pulse_idx ][0]
137 134
             endTimeMs = self.eventTimeL[ self.pulse_idx ][1]
138
-            self.rtAnalyzer.analyze_note( self.audio, self.pitchL[0], begTimeMs, endTimeMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
135
+            self.rtAnalyzer.analyze_note( self.audio, self.pitch, begTimeMs, endTimeMs, self.cfg.analysisArgs['rmsAnalysisArgs'] )
139 136
         
140 137
         self._send_note_off()        
141 138
 
142 139
     
143 140
     def _send_note_off( self ):
144
-        for pitch in self.pitchL:
145
-            self.api.note_off( pitch )
146
-            #print("note-off:",pitch,self.pulse_idx)
141
+        self.api.note_off( self.pitch )
142
+        #print("note-off:",self.pitch,self.pulse_idx)
147 143
 
148 144
     def _disable(self):
149 145
         self.state = None
@@ -153,7 +149,7 @@ class AttackPulseSeq:
153 149
 
154 150
         d = {
155 151
             "pulseUsL":self.pulseUsL,
156
-            "pitchL":self.pitchL,
152
+            "pitch":self.pitch,
157 153
             "noteDurMs":self.noteDurMs,
158 154
             "pauseDurMs":self.pauseDurMs,
159 155
             "holdDutyPctL":self.holdDutyPctL,
@@ -180,16 +176,16 @@ class CalibrateKeys:
180 176
         self.seq      = AttackPulseSeq(  cfg, audioDev, api, noteDurMs=cfg.noteDurMs, pauseDurMs=cfg.pauseDurMs )
181 177
         
182 178
         self.pulseUsL  = None
183
-        self.chordL   = None
179
+        self.pitchL   = None
184 180
         self.pitch_idx = -1
185 181
 
186 182
         
187
-    def start( self, ms, chordL, pulseUsL, playOnlyFl=False ):
188
-        if len(chordL) > 0:
183
+    def start( self, ms, pitchL, pulseUsL, playOnlyFl=False ):
184
+        if len(pitchL) > 0:
189 185
             self.pulseUsL  = pulseUsL
190
-            self.chordL   = chordL
186
+            self.pitchL   = pitchL
191 187
             self.pitch_idx = -1
192
-            self._start_next_chord( ms, playOnlyFl )
188
+            self._start_next_note( ms, playOnlyFl )
193 189
         
194 190
         
195 191
     def stop( self, ms ):
@@ -205,31 +201,27 @@ class CalibrateKeys:
205 201
 
206 202
             # if the sequencer is done playing 
207 203
             if not self.seq.is_enabled():
208
-                self._start_next_chord( ms, self.seq.playOnlyFl ) # ... else start the next sequence
204
+                self._start_next_note( ms, self.seq.playOnlyFl ) # ... else start the next sequence
209 205
 
210 206
         return None
211 207
 
212
-    def _start_next_chord( self, ms, playOnlyFl ):
213
-
208
+    def _start_next_note( self, ms, playOnlyFl ):
214 209
         
215 210
         self.pitch_idx += 1
216 211
 
217
-        # if the last chord in chordL has been played ...
218
-        if self.pitch_idx >= len(self.chordL):
212
+        # if the last note in pitchL has been played ...
213
+        if self.pitch_idx >= len(self.pitchL):
219 214
             self.stop(ms)  # ... then we are done
220 215
         else:
221 216
 
222
-            pitchL = self.chordL[ self.pitch_idx ]
217
+            pitch = self.pitchL[ self.pitch_idx ]
223 218
             
224 219
             # be sure that the base directory exists
225
-            outDir = os.path.expanduser( cfg.outDir )
226
-            if not os.path.isdir( outDir ):
227
-                os.mkdir( outDir )
228
-
229
-            # form the output directory as "<label>_<pitch0>_<pitch1> ... "
230
-            dirStr = "_".join([ str(pitch) for pitch in pitchL ])
220
+            baseDir = os.path.expanduser( cfg.outDir )
221
+            if not os.path.isdir( baseDir ):
222
+                os.mkdir( baseDir )
231 223
 
232
-            outDir = os.path.join(outDir, dirStr )
224
+            outDir = os.path.join(baseDir, str(pitch) )
233 225
 
234 226
             if not os.path.isdir(outDir):
235 227
                 os.mkdir(outDir)
@@ -240,13 +232,16 @@ class CalibrateKeys:
240 232
             print(outDir_id,outDir)
241 233
             
242 234
             # if this is not the first time this note has been sampled then get the resample locations
243
-            if outDir_id != 0:
244
-                self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs )
235
+            if outDir_id == 0:
236
+                self.pulseUsL = self.cfg.full_pulseL
237
+            else:
238
+                #self.pulseUsL,_,_ = form_resample_pulse_time_list( outDir, self.cfg.analysisArgs )
239
+                self.pulseUsL = get_resample_points_wrap( baseDir, pitch, self.cfg.analysisArgs )
245 240
 
246
-            holdDutyPctL = self.cfg.holdDutyPctL
241
+            holdDutyPctL = self.cfg.calibrateArgs['holdDutyPctD'][pitch]
247 242
             
248 243
             if playOnlyFl:
249
-                self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( outDir,  pitchL[0],  self.cfg.analysisArgs, take_id=None )
244
+                self.pulseUsL,_,holdDutyPctL = form_final_pulse_list( outDir,  pitch,  self.cfg.analysisArgs, take_id=None )
250 245
 
251 246
                 noteN = cfg.analysisArgs['auditionNoteN']
252 247
                 self.pulseUsL   = [ self.pulseUsL[ int(round(i*126.0/(noteN-1)))] for i in range(noteN) ]
@@ -258,7 +253,7 @@ class CalibrateKeys:
258 253
                     os.mkdir(outDir)
259 254
 
260 255
             # start the sequencer
261
-            self.seq.start( ms, outDir, pitchL, self.pulseUsL, holdDutyPctL, playOnlyFl )
256
+            self.seq.start( ms, outDir, pitch, self.pulseUsL, holdDutyPctL, playOnlyFl )
262 257
         
263 258
 
264 259
     def _calc_next_out_dir_id( self, outDir ):
@@ -356,8 +351,8 @@ class App:
356 351
         
357 352
 
358 353
     def calibrate_keys_start( self, ms, pitchRangeL ):
359
-        chordL = [ [pitch]  for pitch in range(pitchRangeL[0], pitchRangeL[1]+1)]
360
-        self.cal_keys.start(  ms, chordL, cfg.full_pulseL )
354
+        pitchL = [ pitch  for pitch in range(pitchRangeL[0], pitchRangeL[1]+1)]
355
+        self.cal_keys.start(  ms, pitchL, cfg.full_pulseL )
361 356
 
362 357
     def play_keys_start( self, ms, pitchRangeL ):
363 358
         chordL = [ [pitch]  for pitch in range(pitchRangeL[0], pitchRangeL[1]+1)]

+ 13
- 8
p_ac.yml Datei anzeigen

@@ -27,10 +27,10 @@
27 27
 
28 28
 
29 29
     # MeasureSeq args
30
-    outDir: "~/temp/p_ac_3c",
30
+    outDir: "~/temp/p_ac_3e",
31 31
     noteDurMs: 1000,
32 32
     pauseDurMs: 1000,
33
-    holdDutyPctL: [ [0,50], [22000,55] ],
33
+    #holdDutyPctL: [ [0,50], [22000,55] ],
34 34
 
35 35
     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 36
     full_pulse1L: [  10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 20000, 22000, 24000, 26000, 30000, 32000, 34000, 36000, 40000],
@@ -55,10 +55,15 @@
55 55
       rmsAnalysisArgs: {
56 56
         rmsWndMs: 300,    # length of the RMS measurment window
57 57
         rmsHopMs: 30,     # RMS measurement inter window distance
58
-        dbRefWndMs: 500,  # length of initial portion of signal to use to calculate the dB reference level
58
+        dbLinRef: 0.01,   # length of initial portion of signal to use to calculate the dB reference level
59 59
         harmCandN: 5,     # count of harmonic candidates to locate during harmonic based RMS analysis
60 60
         harmN: 3,         # count of harmonics to use to calculate harmonic based RMS analysis
61
+        durDecayPct: 40,  # percent drop in RMS to indicate the end of a note
61 62
       },
63
+
64
+      resampleMinDb: 10.0,           # note's less than this will be skipped
65
+      resampleNoiseLimitPct: 1.0,    # 
66
+      resampleMinDurMs: 800,         # notes's whose duration is less than this will be skipped
62 67
       
63 68
       minAttkDb: 7.0,   # threshold of silence level 
64 69
       maxDbOffset: 0.25, # travel down the from the max. note level by at most this amount to locate the max. peak
@@ -75,12 +80,12 @@
75 80
       calibrateArgs: {
76 81
 
77 82
         outDir: "~/temp/calib0",
78
-        outLabel: "test",
83
+        outLabel: "test_1",
79 84
         
80 85
         analysisD: {
81 86
           rmsWndMs: 300,    # length of the RMS measurment window
82 87
           rmsHopMs: 30,     # RMS measurement inter window distance
83
-          dbRefWndMs: 500,  # length of initial portion of signal to use to calculate the dB reference level
88
+          dbLinRef: 0.01,   # length of initial portion of signal to use to calculate the dB reference level
84 89
           harmCandN: 5,     # count of harmonic candidates to locate during harmonic based RMS analysis
85 90
           harmN: 3,         # count of harmonics to use to calculate harmonic based RMS analysis
86 91
           durDecayPct: 40   # percent drop in RMS to indicate the end of a note
@@ -90,11 +95,11 @@
90 95
           noteOffDurMs: 1000,
91 96
     
92 97
           
93
-          pitchL: [ 50, 51, 52 ],                    # list of pitches
94
-          targetDbL: [ 16, 20, 23 ],                  # list of target db
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
95 100
 
96 101
           minMeasDurMs: 800,             # minimum candidate note duration
97
-          tolDbPct: 5.0,                 # tolerance as a percent of targetDb above/below used to form match db window
102
+          tolDbPct: 2.0,                 # tolerance as a percent of targetDb above/below used to form match db window
98 103
           maxPulseUs: 45000,             # max. allowable pulse us
99 104
           minPulseUs:  8000,             # min. allowable pulse us
100 105
           initPulseUs: 15000,            # pulseUs for first note

+ 8
- 5
plot_calibrate.ipynb
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 7
- 4
plot_calibrate.py Datei anzeigen

@@ -5,8 +5,9 @@ import matplotlib._color_data as mcd
5 5
 from matplotlib.pyplot import figure
6 6
 
7 7
 from rms_analysis import calibrate_recording_analysis
8
+from rms_analysis import key_info_dictionary
8 9
 
9
-def plot_by_pitch( inDir, pitch=None ):
10
+def plot_by_pitch( inDir, keyInfoD, pitch=None ):
10 11
 
11 12
     anlD = calibrate_recording_analysis( inDir )
12 13
     jsonFn  = os.path.join(inDir, "meas.json" )
@@ -97,7 +98,7 @@ def plot_by_pitch( inDir, pitch=None ):
97 98
                 
98 99
                 
99 100
 
100
-        axL[axi].set_title("pitch:%i " % (midi_pitch))
101
+        axL[axi].set_title("pitch:%i %s" % (midi_pitch,keyInfoD[midi_pitch].type))
101 102
         
102 103
     plt.legend()
103 104
     plt.show()
@@ -140,11 +141,13 @@ if __name__ == "__main__":
140 141
 
141 142
     pitch = None
142 143
     inDir = sys.argv[1]
143
-    if len(sys.argv) > 2:
144
+    yamlFn = sys.argv[2]
145
+    if len(sys.argv) > 3:
144 146
         pitch = int(sys.argv[2])
145 147
 
148
+    keyInfoD = key_info_dictionary( yamlCfgFn=yamlFn)
146 149
     #plot_all_notes( inDir )
147
-    plot_by_pitch(inDir,pitch)
150
+    plot_by_pitch(inDir,keyInfoD,pitch)
148 151
     #calibrate_recording_analysis( inDir )
149 152
     
150 153
     

+ 11
- 10
plot_note_analysis.py Datei anzeigen

@@ -50,6 +50,8 @@ def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
50 50
 
51 51
 
52 52
     #print(rr.tdRmsDbV.shape,rr.rmsDbV.shape)
53
+
54
+    sL = []
53 55
     
54 56
     if sel_note_r is None:
55 57
         print("ERROR: No min note found.")
@@ -65,7 +67,6 @@ def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
65 67
         rr.rmsDbV   = rr.rmsDbV[bi:ei]    
66 68
         offsSec     = bi / rr.rms_srate
67 69
         
68
-        sL = []
69 70
         for r in statsL:
70 71
             begSmpIdx = int(round(r.begSmpSec * rr.rms_srate))
71 72
             endSmpIdx = int(round(r.endSmpSec * rr.rms_srate))
@@ -84,11 +85,11 @@ def select_min_note( rr, statsL, minDurMs=600, minDb=8, contextSecs=10 ):
84 85
     return rr,sL
85 86
         
86 87
 
87
-def plot_note_analysis( inDir ):
88
+def plot_note_audio_reanalysis( inDir ):
88 89
 
89 90
     rmsWndMs=300
90 91
     rmsHopMs=30
91
-    dbRefWndMs=500
92
+    dbLinRef=0.001
92 93
     harmCandN=5
93 94
     harmN=3
94 95
     durDecayPct = 50
@@ -99,21 +100,21 @@ def plot_note_analysis( inDir ):
99 100
     take_id    = int(pathL[-1])
100 101
     midi_pitch = int(pathL[-2])
101 102
 
102
-    r = rms_analysis_main( inDir, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbRefWndMs=dbRefWndMs, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
103
+    r = rms_analysis_main( inDir, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
103 104
 
104 105
     r,statsL = select_min_note(r,r.statsL)
105 106
     
106 107
     do_plot(r,statsL)
107 108
     
108 109
 
109
-def plot_note_analysis_dir( inDir, dirL ):
110
+def plot_note_audio_reanalysis_dir( inDir, dirL ):
110 111
 
111 112
 
112 113
     for folder in dirL:
113 114
 
114 115
         path = os.path.join(inDir,str(folder),"0")
115 116
 
116
-        plot_note_analysis( path )
117
+        plot_note_audio_reanalysis( path )
117 118
         
118 119
         
119 120
 
@@ -127,7 +128,7 @@ def get_all_note_durations( inDir, cacheFn ):
127 128
         takeId = 0
128 129
         rmsWndMs=300
129 130
         rmsHopMs=30
130
-        dbRefWndMs=500
131
+        dbLinRef=0.001
131 132
         harmCandN=5
132 133
         harmN=3
133 134
         durDecayPct = 40
@@ -139,7 +140,7 @@ def get_all_note_durations( inDir, cacheFn ):
139 140
 
140 141
         if os.path.isfile(os.path.join(takePath,'seq.json')):
141 142
             print(midi_pitch)
142
-            r = rms_analysis_main( takePath, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbRefWndMs=dbRefWndMs, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
143
+            r = rms_analysis_main( takePath, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
143 144
 
144 145
             xL = []
145 146
             for i,sr in enumerate(r.statsL):
@@ -319,7 +320,7 @@ if __name__ == "__main__":
319 320
     pitchL = [ 30,31,32,33,34,35 ]
320 321
     pitchL = [ 70,71,72,73,74,75 ]
321 322
     
322
-    #plot_note_analysis_dir( "/home/kevin/temp/p_ac_3c",pitchL)
323
+    plot_note_audio_reanalysis_dir( "/home/kevin/temp/p_ac_3c",pitchL)
323 324
 
324 325
     durFn = "/home/kevin/temp/cache_note_dur.pickle"
325 326
     #get_all_note_durations("/home/kevin/temp/p_ac_3c",durFn)
@@ -327,4 +328,4 @@ if __name__ == "__main__":
327 328
 
328 329
     #plot_quiet_note_db(durFn,"p_ac.yml")
329 330
 
330
-    dump_hold_duty_pct( "/home/kevin/temp/p_ac_3c" )
331
+    #dump_hold_duty_pct( "/home/kevin/temp/p_ac_3c" )

+ 45
- 7
plot_seq.py Datei anzeigen

@@ -5,6 +5,7 @@ from common import parse_yaml_cfg
5 5
 from rms_analysis import rms_analysis_main
6 6
 from rms_analysis import select_first_stable_note_by_delta_db
7 7
 from rms_analysis import select_first_stable_note_by_dur
8
+from rms_analysis import samples_to_linear_residual
8 9
 
9 10
 def is_nanV( xV ):
10 11
     
@@ -24,7 +25,7 @@ def _find_max_take_id( inDir ):
24 25
         id -= 1
25 26
         
26 27
     return id
27
-        
28
+
28 29
 
29 30
 def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
30 31
 
@@ -40,6 +41,10 @@ def form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None ):
40 41
 
41 42
         take_number = int(idir)
42 43
 
44
+
45
+        if not os.path.isfile(os.path.join( inDir,idir, "seq.json")):
46
+            continue
47
+
43 48
         # analyze this takes audio and locate the note peaks
44 49
         r = rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD['rmsAnalysisArgs'] )
45 50
 
@@ -234,9 +239,14 @@ def form_resample_pulse_time_list( inDir, analysisArgsD ):
234 239
 
235 240
     return resampleUsL, pkDbL, pkUsL
236 241
 
242
+def plot_curve( ax, pulseUsL, rmsDbV ):
237 243
 
244
+    coeff = np.polyfit(pulseUsL,rmsDbV,5)
245
+    func  = np.poly1d(coeff)
238 246
 
239
-def plot_resample_pulse_times( inDir, analysisArgsD ):
247
+    ax.plot( pulseUsL, func(pulseUsL), color='red')
248
+
249
+def plot_resample_pulse_times_0( inDir, analysisArgsD ):
240 250
 
241 251
     newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
242 252
 
@@ -245,15 +255,43 @@ def plot_resample_pulse_times( inDir, analysisArgsD ):
245 255
     
246 256
     fig,ax = plt.subplots()
247 257
 
248
-    ax.plot(pulseUsL,rmsDbV )
258
+    ax.plot(pulseUsL,rmsDbV,marker='.' )
249 259
 
250 260
     for us in newPulseUsL:
251 261
         ax.axvline( x = us )
252 262
 
253 263
     ax.plot(velTblUsL,velTblDbL,marker='.',linestyle='None')
264
+
254 265
     
255 266
     plt.show()
256
-        
267
+
268
+def plot_resample_pulse_times( inDir, analysisArgsD ):
269
+
270
+    newPulseUsL, rmsDbV, pulseUsL = form_resample_pulse_time_list( inDir, analysisArgsD )
271
+
272
+    midi_pitch = int( inDir.split("/")[-1] )
273
+    velTblUsL,velTblDbL,_ = form_final_pulse_list( inDir, midi_pitch, analysisArgsD, take_id=None )
274
+    
275
+    fig,axL = plt.subplots(2,1,gridspec_kw={'height_ratios': [2, 1]})
276
+
277
+    axL[0].plot(pulseUsL,rmsDbV,marker='.' )
278
+
279
+    #plot_curve( ax, velTblUsL,velTblDbL)
280
+    
281
+    scoreV = samples_to_linear_residual( pulseUsL, rmsDbV)
282
+
283
+    axL[0].plot(pulseUsL,rmsDbV + scoreV)
284
+    axL[0].plot(pulseUsL,rmsDbV + np.power(scoreV,2.0))
285
+    axL[0].plot(pulseUsL,rmsDbV - np.power(scoreV,2.0))
286
+
287
+    axL[1].axhline(0.0,color='black')
288
+    axL[1].axhline(1.0,color='black')
289
+    axL[1].plot(pulseUsL,np.abs(scoreV * 100.0 / rmsDbV))
290
+    axL[1].set_ylim((0.0,50))
291
+    plt.show()
292
+
293
+
294
+
257 295
 def find_min_max_peak_index( pkDbL, minDb, maxDbOffs ):
258 296
     """
259 297
     Find the min db and max db peak.
@@ -348,7 +386,7 @@ def plot_spectrum( ax, srate, binHz, specV, midiPitch, harmN ):
348 386
 
349 387
     ax.set_ylabel(str(midiPitch))
350 388
         
351
-def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbRefWndMs=500 ):
389
+def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbLinRef=0.001 ):
352 390
     """ Plot the spectrum from one note (7th from last) in each attack pulse length sequence referred to by pitchL."""
353 391
     
354 392
     plotN = len(pitchL)
@@ -369,7 +407,7 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR
369 407
         sigV  = signalM / float(0x7fff)
370 408
 
371 409
         # calc. the RMS envelope in the time domain
372
-        rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs )
410
+        rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
373 411
 
374 412
         # locate the sample index of the peak of each note attack 
375 413
         pkIdx0L = locate_peak_indexes( rms0DbV, rms0_srate, r['eventTimeL'] )
@@ -383,7 +421,7 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR
383 421
 
384 422
 
385 423
         # calc. the RMS envelope by taking the max spectral peak in each STFT window 
386
-        rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, spectrumSmpIdx)
424
+        rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, spectrumSmpIdx)
387 425
 
388 426
         # specV[] is the spectrum of the note at spectrumSmpIdx
389 427
 

+ 248
- 0
plot_seq_1.py Datei anzeigen

@@ -0,0 +1,248 @@
1
+import os, sys
2
+import matplotlib.pyplot as plt
3
+import numpy as np
4
+from common import parse_yaml_cfg
5
+import rms_analysis
6
+
7
+def get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD ):
8
+    
9
+    inDir = os.path.join(inDir,"%i" % (midi_pitch))
10
+
11
+    dirL =  os.listdir(inDir)
12
+
13
+    pkL = []
14
+
15
+    # for each take in this directory
16
+    for idir in dirL:
17
+
18
+        take_number = int(idir)
19
+
20
+        # analyze this takes audio and locate the note peaks
21
+        r = rms_analysis.rms_analysis_main( os.path.join(inDir,idir), midi_pitch, **analysisArgsD )
22
+
23
+        # store the peaks in pkL[ (db,us) ]
24
+        for db,us,stats in zip(r.pkDbL,r.pkUsL,r.statsL):
25
+            pkL.append( (db,us,stats.durMs,take_number) )
26
+
27
+
28
+            
29
+    # sort the peaks on increasing attack pulse microseconds
30
+    pkL = sorted( pkL, key= lambda x: x[1] )
31
+
32
+    # merge sample points that separated by less than 'minSampleDistUs' milliseconds
33
+    #pkL = merge_close_sample_points( pkL, analysisArgsD['minSampleDistUs'] )
34
+    
35
+    # split pkL 
36
+    pkDbL,pkUsL,durMsL,takeIdL = tuple(zip(*pkL))
37
+
38
+    return pkUsL,pkDbL,durMsL,takeIdL,r.holdDutyPctL
39
+
40
+def select_resample_reference_indexes( noiseIdxL ):
41
+
42
+    resampleIdxS = set()
43
+
44
+    for i in noiseIdxL:
45
+        resampleIdxS.add( i )
46
+        resampleIdxS.add( i+1 )
47
+        resampleIdxS.add( i-1 )
48
+
49
+    resampleIdxL = list(resampleIdxS)
50
+
51
+    # if a single sample point is left out of a region of
52
+    # contiguous sample points then include this as a resample point
53
+    for i in resampleIdxL:
54
+        if i + 1 not in resampleIdxL and i + 2 in resampleIdxL:  # BUG BUG BUG: Hardcoded constant
55
+            resampleIdxL.append(i+1)
56
+
57
+    return resampleIdxL
58
+
59
+def locate_resample_regions( usL, dbL, resampleIdxL ):
60
+
61
+    # locate regions of points to resample
62
+    regionL = []  # (bi,ei)
63
+    inRegionFl = False
64
+    bi = None
65
+    for i in range(len(usL)):
66
+        if inRegionFl:            
67
+            if i not in resampleIdxL:
68
+                regionL.append((bi,i-1))
69
+                inRegionFl = False
70
+                bi         = None
71
+        else:
72
+            if i in resampleIdxL:
73
+                inRegionFl = True
74
+                bi         = i
75
+                
76
+    if bi is not None:
77
+        regionL.append((bi,len(usL)-1))
78
+
79
+    # select points around and within the resample regions
80
+    # to resample
81
+    reUsL = []
82
+    reDbL = []
83
+    for bi,ei in regionL:
84
+
85
+        for i in range(bi,ei+2):
86
+            if i == 0:
87
+                us = usL[i]
88
+                db = dbL[i]
89
+            elif i >= len(usL):
90
+                us = usL[i-1]
91
+                db = dbL[i-1]
92
+            else:
93
+                us = usL[i-1] + (usL[i]-usL[i-1])/2 
94
+                db = dbL[i-1] + (dbL[i]-dbL[i-1])/2
95
+                
96
+            reUsL.append(us)
97
+            reDbL.append(db)
98
+
99
+
100
+    return reUsL,reDbL
101
+
102
+def get_dur_skip_indexes( durMsL, dbL, takeIdL,  minDurMs, minDb ):
103
+
104
+    firstAudibleIdx = None
105
+    firstNonSkipIdx = None
106
+    skipIdxL = [ i for i,(ms,db) in enumerate(zip(durMsL,dbL)) if ms < minDurMs or db < minDb ]
107
+
108
+    # if a single sample point is left out of a region of
109
+    # contiguous skipped points then skip this point also
110
+    for i in range(len(durMsL)):
111
+        if i not in skipIdxL and i-1 in skipIdxL and i+1 in skipIdxL:
112
+            skipIdxL.append(i)
113
+
114
+    # find the first set of 3 contiguous samples that
115
+    # are greater than minDurMs - all samples prior
116
+    # to these will be skipped
117
+    xL = []
118
+    for i in range(len(durMsL)):
119
+        if i in skipIdxL:
120
+            xL = []
121
+        else:
122
+            xL.append(i)
123
+
124
+        if len(xL) == 3:  # BUG BUG BUG: Hardcoded constant
125
+            firstAudibleIdx = xL[0]
126
+            break
127
+        
128
+
129
+    # decrease by one decibel to locate the first non-skip
130
+
131
+    # TODO: what if no note exists that is one decibel less
132
+    #       The recordings of very quiet notes do not give reliabel decibel measures
133
+    #       so this may not be the best backup criteria
134
+    
135
+    if firstAudibleIdx is not None:
136
+        i = firstAudibleIdx-1
137
+        while abs(dbL[i] - dbL[firstAudibleIdx]) < 1.0:  # BUG BUG BUG: Hardcoded constant
138
+            i -= 1
139
+
140
+        firstNonSkipIdx = i
141
+        
142
+    return skipIdxL, firstAudibleIdx, firstNonSkipIdx
143
+
144
+def get_resample_points( usL, dbL, durMsL, takeIdL, minDurMs, minDb, noiseLimitPct ):
145
+
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
+    scoreV       = np.abs( rms_analysis.samples_to_linear_residual( usL, dbL) * 100.0 / dbL )
150
+    noiseIdxL    = [ i for i in range(scoreV.shape[0]) if scoreV[i] > noiseLimitPct ]
151
+    noiseL       = [ (usL[i],dbL[i]) for i in noiseIdxL ]
152
+    resampleIdxL = select_resample_reference_indexes( noiseIdxL )
153
+    resampleIdxL = [ i for i in resampleIdxL if i >= firstNonSkipIdx ]
154
+    resampleL    = [ (usL[i],dbL[i]) for i in resampleIdxL   ]
155
+    reUsL,reDbL  = locate_resample_regions( usL, dbL, resampleIdxL )
156
+
157
+    return reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx
158
+
159
+def get_resample_points_wrap( inDir, midi_pitch, analysisArgsD ):
160
+
161
+    usL, dbL, durMsL,_,_ = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
162
+
163
+    reUsL,_,_,_,_,_,_ = get_resample_points( usL, dbL, durMsL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
164
+    
165
+    return reUsL
166
+
167
+def plot_noise_region( ax, inDir, keyMapD, midi_pitch, analysisArgsD ):
168
+
169
+    plotResampleFl = False
170
+    plotTakesFl    = True
171
+    usL, dbL, durMsL, takeIdL, holdDutyPctL = get_merged_pulse_db_measurements( inDir, midi_pitch, analysisArgsD['rmsAnalysisArgs'] )
172
+
173
+    reUsL, reDbL, noiseL, resampleL, durL, firstAudibleIdx, firstNonSkipIdx  = get_resample_points( usL, dbL, durMsL, takeIdL, analysisArgsD['resampleMinDurMs'], analysisArgsD['resampleMinDb'], analysisArgsD['resampleNoiseLimitPct'] )
174
+
175
+    # 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')
178
+    
179
+    # plot the resample points
180
+    if plotResampleFl:
181
+        ax.plot( reUsL, reDbL, markersize=10, marker='x', linestyle='None', color='green')
182
+
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
+
193
+    # plot actual sample points
194
+    if plotTakesFl:
195
+        for takeId in list(set(takeIdL)):
196
+            xL,yL = zip(*[(usL[i],dbL[i]) for i in range(len(usL)) if takeIdL[i]==takeId  ])
197
+            ax.plot(xL,yL, marker='.')
198
+            for i,(x,y) in enumerate(zip(xL,yL)):
199
+                ax.text(x,y,str(i))
200
+    else:
201
+        ax.plot(usL, dbL, marker='.')
202
+
203
+    # plot the duration skip points
204
+    if durL:
205
+        nUsL,nDbL    = zip(*durL)
206
+        ax.plot( nUsL, nDbL, marker='.', linestyle='None', color='yellow')
207
+
208
+    # plot the locations where the hold duty cycle changes with vertical black lines
209
+    for us_duty in holdDutyPctL:
210
+        us,duty = tuple(us_duty)
211
+        if us > 0:
212
+            ax.axvline(us,color='black')
213
+
214
+    # plot the 'minDb' reference line
215
+    ax.axhline(analysisArgsD['resampleMinDb'] ,color='black')
216
+    
217
+    
218
+    ax.set_ylabel( "%i %s %s" % (midi_pitch, keyMapD[midi_pitch]['type'],keyMapD[midi_pitch]['class']))
219
+    
220
+def plot_noise_regions_main( inDir, cfg, pitchL ):
221
+
222
+    analysisArgsD = cfg.analysisArgs
223
+    keyMapD = { d['midi']:d for d in cfg.key_mapL }
224
+    axN = len(pitchL)
225
+    fig,axL = plt.subplots(axN,1)
226
+    if axN == 1:
227
+        axL = [axL]
228
+    fig.set_size_inches(18.5, 10.5*axN)
229
+
230
+    for ax,midi_pitch in zip(axL,pitchL):
231
+        plot_noise_region( ax,inDir, cfg.key_mapL, midi_pitch, analysisArgsD )
232
+
233
+    plt.show()
234
+
235
+
236
+if __name__ == "__main__":
237
+
238
+    inDir = sys.argv[1]
239
+    cfgFn = sys.argv[2]
240
+    pitch = int(sys.argv[3])
241
+
242
+    cfg = parse_yaml_cfg( cfgFn )
243
+
244
+    pitchL = [pitch]
245
+        
246
+    plot_noise_regions_main( inDir, cfg, pitchL )
247
+    
248
+    #rms_analysis.write_audacity_label_files( inDir, cfg.analysisArgs['rmsAnalysisArgs'] )

+ 76
- 0
plot_us_db_range.ipynb
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 114
- 34
rms_analysis.py Datei anzeigen

@@ -26,15 +26,16 @@ def calc_harm_bins( srate, binHz, midiPitch, harmN ):
26 26
     
27 27
     return fund_l_binL, fund_m_binL, fund_u_binL
28 28
     
29
-def rms_to_db( xV, rms_srate, refWndMs ):
29
+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
-    dbRef = refWndMs   ######################################################### HACK HACK HACK HACK HACK
33
-    rmsDbV = 20.0 * np.log10( xV / dbRef )
32
+
33
+    #print("DB REF:",dbLinRef)
34
+    rmsDbV = 20.0 * np.log10( xV / dbLinRef )
34 35
 
35 36
     return rmsDbV
36 37
 
37
-def audio_rms( srate, xV, rmsWndMs, hopMs, refWndMs  ):
38
+def audio_rms( srate, xV, rmsWndMs, hopMs, dbLinRef  ):
38 39
 
39 40
     wndSmpN = int(round( rmsWndMs * srate / 1000.0))
40 41
     hopSmpN = int(round( hopMs    * srate / 1000.0))
@@ -61,10 +62,10 @@ def audio_rms( srate, xV, rmsWndMs, hopMs, refWndMs  ):
61 62
         j += 1
62 63
 
63 64
     rms_srate = srate / hopSmpN
64
-    return rms_to_db( yV, rms_srate, refWndMs ), rms_srate
65
+    return rms_to_db( yV, rms_srate, dbLinRef ), rms_srate
65 66
 
66 67
 
67
-def audio_stft_rms( srate, xV, rmsWndMs, hopMs, refWndMs, spectrumIdx ):
68
+def audio_stft_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, spectrumIdx ):
68 69
     
69 70
     wndSmpN = int(round( rmsWndMs * srate / 1000.0))
70 71
     hopSmpN = int(round( hopMs    * srate / 1000.0))
@@ -82,12 +83,12 @@ def audio_stft_rms( srate, xV, rmsWndMs, hopMs, refWndMs, spectrumIdx ):
82 83
 
83 84
 
84 85
     rms_srate = srate / hopSmpN
85
-    mV = rms_to_db( mV, rms_srate, refWndMs )
86
+    mV = rms_to_db( mV, rms_srate, dbLinRef )
86 87
         
87 88
     return mV, rms_srate, specV, specHopIdx, binHz
88 89
 
89 90
 
90
-def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, midiPitch, harmCandN, harmN  ):
91
+def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbLinRef, midiPitch, harmCandN, harmN  ):
91 92
 
92 93
     wndSmpN   = int(round( rmsWndMs * srate / 1000.0))
93 94
     hopSmpN   = int(round( hopMs    * srate / 1000.0))
@@ -116,7 +117,7 @@ def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, midiPitch, harmCandN
116 117
             
117 118
         
118 119
     rms_srate = srate / hopSmpN
119
-    rmsV = rms_to_db( rmsV, rms_srate, dbRefWndMs )
120
+    rmsV = rms_to_db( rmsV, rms_srate, dbLinRef )
120 121
     return rmsV, rms_srate, binHz
121 122
     
122 123
 def measure_duration_ms( rmsV, rms_srate, peak_idx, end_idx, decay_pct ):
@@ -257,26 +258,20 @@ def key_info_dictionary( keyMapL=None, yamlCfgFn=None):
257 258
 
258 259
     return kmD
259 260
 
260
-def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=500, harmCandN=5, harmN=3, durDecayPct=40 ):
261
+def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
261 262
 
262 263
     sigV = np.squeeze(sigV)
263 264
 
264
-                        # HACK HACK HACK HACK
265
-    dbRefWndMs = 0.002  # HACK HACK HACK HACK
266
-                        # HACK HACK HACK HACK
267
-    td_rmsDbV, td_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs )
265
+    td_rmsDbV, td_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
268 266
 
269 267
     begSmpIdx = int(round(begMs * td_srate/1000))
270 268
     endSmpIdx = int(round(endMs * td_srate/1000))
269
+    
271 270
     td_pk_idx = begSmpIdx + np.argmax(td_rmsDbV[begSmpIdx:endSmpIdx])
272 271
 
273 272
     td_durMs = measure_duration_ms( td_rmsDbV, td_srate, td_pk_idx, len(sigV)-1, durDecayPct )
274 273
 
275
-                       # HACK HACK HACK HACK    
276
-    dbRefWndMs = 0.01  # HACK HACK HACK HACK
277
-                       # HACK HACK HACK HACK
278
-     
279
-    hm_rmsDbV, hm_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, midi_pitch, harmCandN, harmN  )
274
+    hm_rmsDbV, hm_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN  )
280 275
 
281 276
     begSmpIdx = int(round(begMs * hm_srate/1000))
282 277
     endSmpIdx = int(round(endMs * hm_srate/1000))
@@ -289,6 +284,7 @@ def rms_analyze_one_rt_note( sigV, srate, begMs, endMs, midi_pitch, rmsWndMs=300
289 284
     
290 285
     return { "td":tdD, "hm":hmD }
291 286
 
287
+
292 288
 def calibrate_rms( sigV, srate, beg_ms, end_ms ):
293 289
 
294 290
     bi = int(round(beg_ms * srate / 1000))
@@ -321,11 +317,7 @@ def calibrate_recording_analysis( inDir ):
321 317
 
322 318
     anlr = types.SimpleNamespace(**cfg.analysisD)
323 319
 
324
-                        # HACK HACK HACK HACK
325
-    dbRefWndMs = 0.002  # HACK HACK HACK HACK
326
-                        # HACK HACK HACK HACK
327
-    
328
-    tdRmsDbV, td_srate = audio_rms( srate, sigV, anlr.rmsWndMs, anlr.rmsHopMs, dbRefWndMs )
320
+    tdRmsDbV, td_srate = audio_rms( srate, sigV, anlr.rmsWndMs, anlr.rmsHopMs, anlr.dbLinRef )
329 321
 
330 322
     # for each measured pitch 
331 323
     for midi_pitch,measL in measD.items():
@@ -358,7 +350,7 @@ def calibrate_recording_analysis( inDir ):
358 350
         
359 351
 
360 352
 
361
-def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=500, harmCandN=5, harmN=3, durDecayPct=40 ):
353
+def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
362 354
 
363 355
     seqFn     = os.path.join( inDir, "seq.json")
364 356
     audioFn   = os.path.join( inDir, "audio.wav")
@@ -371,15 +363,13 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
371 363
     srate, signalM  = wavfile.read(audioFn)
372 364
     sigV  = signalM / float(0x7fff)
373 365
 
374
-    tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs )
366
+    tdRmsDbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef )
375 367
 
376 368
     tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate,  r['eventTimeL'])
377 369
     
378
-    rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, midi_pitch, harmCandN, harmN  )
379
-    
380
-    pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'] )
381
-
370
+    rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbLinRef, midi_pitch, harmCandN, harmN  )
382 371
     
372
+    pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'] )    
383 373
 
384 374
     holdDutyPctL = None
385 375
     if 'holdDutyPct' in r:
@@ -390,6 +380,7 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
390 380
 
391 381
     r = types.SimpleNamespace(**{
392 382
         "audio_srate":srate,
383
+        "eventTimeMsL":r['eventTimeL'],
393 384
         "tdRmsDbV": tdRmsDbV,
394 385
         "tdPkIdxL": tdPkIdxL,
395 386
         "tdPkDbL": [ tdRmsDbV[i] for i in tdPkIdxL ],
@@ -397,8 +388,6 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
397 388
         "rmsDbV":rmsDbV,
398 389
         "rms_srate":rms_srate,
399 390
         "pkIdxL":pkIdxL,            # pkIdxL[ len(pulsUsL) ] - indexes into rmsDbV[] of peaks
400
-        #"min_pk_idx":min_pk_idx,
401
-        #"max_pk_idx":max_pk_idx,
402 391
         "eventTimeL":r['eventTimeL'],
403 392
         "holdDutyPctL":holdDutyPctL,
404 393
         'pkDbL': [ rmsDbV[ i ] for i in pkIdxL ],
@@ -412,7 +401,7 @@ def rms_analysis_main( inDir, midi_pitch, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=
412 401
     return r
413 402
 
414 403
 
415
-def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbRefWndMs=500, harmCandN=5, harmN=3, durDecayPct=40 ):
404
+def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbLinRef=0.001, harmCandN=5, harmN=3, durDecayPct=40 ):
416 405
 
417 406
     if os.path.isfile(cacheFn):
418 407
         print("READING analysis cache file: %s" % (cacheFn))
@@ -436,7 +425,7 @@ def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbRefWndMs
436 425
         path = os.path.join(inDir,folder,'0')
437 426
 
438 427
         if os.path.isdir(path) and os.path.isfile(os.path.join(os.path.join(path,"seq.json"))):
439
-            r = rms_analysis_main( path, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbRefWndMs=dbRefWndMs, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
428
+            r = rms_analysis_main( path, midi_pitch, rmsWndMs=rmsWndMs, rmsHopMs=rmsHopMs, dbLinRef=dbLinRef, harmCandN=harmCandN, harmN=harmN, durDecayPct=durDecayPct )
440 429
 
441 430
             rD[ midi_pitch ] = r
442 431
 
@@ -445,3 +434,94 @@ def rms_analysis_main_all( inDir, cacheFn, rmsWndMs=300, rmsHopMs=30, dbRefWndMs
445 434
         pickle.dump(rD,f)
446 435
         
447 436
     return rD
437
+
438
+
439
+
440
+def samples_to_linear_residual( usL, dbL, pointsPerLine=5 ):
441
+    # Score the quality of each sampled point by measuring the
442
+    # quality of fit to a local line. 
443
+    
444
+    scoreD = { us:[] for us in usL }
445
+
446
+    pointsPerLine = 5
447
+
448
+    i = pointsPerLine
449
+    
450
+    # for each sampled point
451
+    while i < len(usL):
452
+        # beginning with sample at index 'pointsPerLine' 
453
+        if i >= pointsPerLine:
454
+            
455
+            k     = i - pointsPerLine
456
+
457
+            # get the x (us) and y (db) sample values
458
+            xL,yL = zip(*[ ((usL[k+j],1.0), dbL[k+j])  for j in range(pointsPerLine)])
459
+            xV    = np.array(xL)
460
+            yV    = np.array(yL)
461
+            
462
+            # fit the sampled point to a line
463
+            m,c   = np.linalg.lstsq(xV,yV,rcond=None)[0]
464
+
465
+            # calc the residual of the fit at each point
466
+            resV  = (m*xV+c)[:,0] - yV
467
+
468
+            # assign the residual to the associated point in scoreD[x]
469
+            for j in range(pointsPerLine):
470
+                scoreD[usL[k+j]].append(resV[j])
471
+            
472
+        i += 1
473
+            
474
+
475
+    scoreL = []
476
+
477
+    # calc the mean of the residuals for each point
478
+    # (most points were used in 'pointsPerLine' line estimations
479
+    #  and so they will have 'pointsPerLine' residual values)
480
+    for us in usL:
481
+
482
+        resL = scoreD[us]
483
+        
484
+        if len(resL) == 0:
485
+            scoreL.append(0.0)
486
+        elif len(resL) == 1:
487
+            scoreL.append(resL[0])
488
+        else:
489
+            scoreL.append(np.mean(resL))
490
+
491
+    # their should be one mean resid. value for each sampled point
492
+    assert( len(scoreL) == len(usL) )
493
+    return np.array(scoreL)
494
+        
495
+def write_audacity_label_files( inDir, analysisArgsD ):
496
+
497
+    pitchDirL = os.listdir(inDir)
498
+
499
+    for pitchDir in pitchDirL:
500
+        
501
+        folderL = pitchDir.split(os.sep)
502
+
503
+        midi_pitch = int(folderL[-1])
504
+
505
+        pitchDir = os.path.join(inDir,pitchDir)
506
+
507
+        takeDirL = os.listdir(pitchDir)
508
+
509
+        for takeFolder in takeDirL:
510
+
511
+            takeDir = os.path.join(pitchDir,takeFolder)
512
+
513
+            r = rms_analysis_main( takeDir, midi_pitch, **analysisArgsD )
514
+
515
+            labelFn = os.path.join(takeDir,"audacity.txt")
516
+
517
+            print("Writing:",labelFn)
518
+
519
+            with open(labelFn,"w") as f:
520
+                
521
+                for i,s in enumerate(r.statsL):
522
+
523
+                    label = "%i %4.1f %6.1f" % (i, s.pkDb, s.durMs )
524
+                    
525
+                    f.write("%f\t%f\t%s\n" % ( s.begSmpSec, s.endSmpSec, label ))
526
+
527
+            

+ 2
- 2
rt_note_analysis.py Datei anzeigen

@@ -33,7 +33,7 @@ class RT_Analyzer:
33 33
 
34 34
             anlArgs = types.SimpleNamespace(**anlArgD)
35 35
 
36
-            rmsDbV, rms_srate, binHz = audio_harm_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbRefWndMs, midi_pitch, anlArgs.harmCandN, anlArgs.harmN  )
36
+            rmsDbV, rms_srate, binHz = audio_harm_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbLinRef, midi_pitch, anlArgs.harmCandN, anlArgs.harmN  )
37 37
 
38 38
             pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, [( begTimeMs, endTimeMs)] )
39 39
 
@@ -46,7 +46,7 @@ class RT_Analyzer:
46 46
 
47 47
                 hm_db = rmsDbV[ pkIdxL[0] ]
48 48
 
49
-            tdRmsDbV, rms0_srate = audio_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbRefWndMs )
49
+            tdRmsDbV, rms0_srate = audio_rms( audioDev.srate, np.squeeze(sigV), anlArgs.rmsWndMs, anlArgs.rmsHopMs, anlArgs.dbLinRef )
50 50
 
51 51
             tdPkIdxL = locate_peak_indexes( tdRmsDbV, rms0_srate,  [( begTimeMs, endTimeMs)] )
52 52
 

Laden…
Abbrechen
Speichern