From ac9061a72d109b2f20297aa6108a87441efd3e4e Mon Sep 17 00:00:00 2001 From: kpl Date: Tue, 20 Aug 2019 20:45:53 -0400 Subject: [PATCH] Added min/max volume note and skip note detection. --- plot_seq.py | 127 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 45 deletions(-) diff --git a/plot_seq.py b/plot_seq.py index c0d0de3..85125f0 100644 --- a/plot_seq.py +++ b/plot_seq.py @@ -11,6 +11,49 @@ def is_nanV( xV ): return True return False + +def find_min_max_peak_index( rmsV, pkIdxL, minDb, maxDbOffs=0.5 ): + + # select only the peaks from rmsV[] to work with + yV = rmsV[ pkIdxL ] + + # get the max volume note + max_i = np.argmax( yV ) + maxDb = yV[ max_i ] + + min_i = max_i + + # starting from the max volume peak go backwards + for i in range( max_i, 0, -1 ): + + # if this peak is within maxDbOffs of the loudest then choose this one instead + if maxDb - yV[i] < maxDbOffs: + max_i = i + + # if this peak is less than minDb then the previous note is the min note + if yV[i] < minDb: + break + + min_i = i + + assert( min_i < max_i ) + + return min_i, max_i + +def find_skip_peaks( rmsV, pkIdxL, min_pk_idx, max_pk_idx ): + skipPkIdxL = [] + yV = rmsV[pkIdxL] + refPkDb = yV[min_pk_idx] + + for i in range( min_pk_idx+1, max_pk_idx+1 ): + if yV[i] > refPkDb: + refPkDb = yV[i] + else: + skipPkIdxL.append(i) + + + return skipPkIdxL + def calc_harm_bins( srate, binHz, midiPitch, harmN ): @@ -33,9 +76,14 @@ def calc_harm_bins( srate, binHz, midiPitch, harmN ): return fund_l_binL, fund_m_binL, fund_u_binL +def rms_to_db( xV, rms_srate, refWndMs ): + dbWndN = int(round(refWndMs * rms_srate / 1000.0)) + dbRef = ref = np.mean(xV[0:dbWndN]) + rmsDbV = 20.0 * np.log10( xV / dbRef ) + return rmsDbV -def audio_rms( srate, xV, rmsWndMs, hopMs ): +def audio_rms( srate, xV, rmsWndMs, hopMs, refWndMs ): wndSmpN = int(round( rmsWndMs * srate / 1000.0)) hopSmpN = int(round( hopMs * srate / 1000.0)) @@ -60,19 +108,12 @@ def audio_rms( srate, xV, rmsWndMs, hopMs ): i += hopSmpN j += 1 - - return yV, srate / hopSmpN -def audio_db_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs ): - - rmsV, rms_srate = audio_rms( srate, xV, rmsWndMs, hopMs ) - dbWndN = int(round(dbRefWndMs * rms_srate / 1000.0)) - dbRef = ref = np.mean(rmsV[0:dbWndN]) - return 20.0 * np.log10( rmsV / dbRef ), rms_srate + rms_srate = srate / hopSmpN + return rms_to_db( yV, rms_srate, refWndMs ), rms_srate - -def audio_stft_rms( srate, xV, rmsWndMs, hopMs, spectrumIdx ): +def audio_stft_rms( srate, xV, rmsWndMs, hopMs, refWndMs, spectrumIdx ): wndSmpN = int(round( rmsWndMs * srate / 1000.0)) hopSmpN = int(round( hopMs * srate / 1000.0)) @@ -88,18 +129,14 @@ def audio_stft_rms( srate, xV, rmsWndMs, hopMs, spectrumIdx ): for i in range(xM.shape[1]): mV[i] = np.max(np.sqrt(np.abs(xM[:,i]))) - return mV, srate / hopSmpN, specV, specHopIdx, binHz -def audio_stft_db_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, spectrumIdx ): - rmsV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, xV, rmsWndMs, hopMs, spectrumIdx ) - - dbWndN = int(round(dbRefWndMs * rms_srate / 1000.0)) - dbRef = ref = np.mean(rmsV[0:dbWndN]) - rmsDbV = 20.0 * np.log10( rmsV / dbRef ) + rms_srate = srate / hopSmpN + mV = rms_to_db( mV, rms_srate, refWndMs ) + + return mV, rms_srate, specV, specHopIdx, binHz - return rmsDbV, rms_srate, specV, specHopIdx, binHz -def audio_harm_rms( srate, xV, rmsWndMs, hopMs, midiPitch, harmCandN, harmN ): +def audio_harm_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, midiPitch, harmCandN, harmN ): wndSmpN = int(round( rmsWndMs * srate / 1000.0)) hopSmpN = int(round( hopMs * srate / 1000.0)) @@ -125,20 +162,9 @@ def audio_harm_rms( srate, xV, rmsWndMs, hopMs, midiPitch, harmCandN, harmN ): - - return rmsV, srate / hopSmpN, binHz - - - -def audio_harm_db_rms( srate, xV, rmsWndMs, hopMs, dbRefWndMs, midiPitch, harmCandN, harmN ): - - rmsV, rms_srate, binHz = audio_harm_rms( srate, xV, rmsWndMs, hopMs, midiPitch, harmCandN, harmN ) - - dbWndN = int(round(dbRefWndMs * rms_srate / 1000.0)) - dbRef = ref = np.mean(rmsV[0:dbWndN]) - rmsDbV = 20.0 * np.log10( rmsV / dbRef ) - - return rmsDbV, rms_srate, binHz + rms_srate = srate / hopSmpN + rmsV = rms_to_db( rmsV, rms_srate, dbRefWndMs ) + return rmsV, rms_srate, binHz @@ -156,7 +182,8 @@ def locate_peak_indexes( xV, xV_srate, eventMsL ): def plot_spectrum( ax, srate, binHz, specV, midiPitch, harmN ): - + """ Plot a single spectrum, 'specV' and the harmonic peak location boundaries.""" + binN = specV.shape[0] harmLBinL,harmMBinL,harmUBinL = calc_harm_bins( srate, binHz, midiPitch, harmN ) @@ -178,7 +205,8 @@ def plot_spectrum( ax, srate, binHz, specV, midiPitch, harmN ): ax.set_ylabel(str(midiPitch)) def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbRefWndMs=500 ): - + """ Plot the spectrum from one note (7th from last) in each attack pulse length sequence referred to by pitchL.""" + plotN = len(pitchL) fig,axL = plt.subplots(plotN,1) @@ -197,7 +225,7 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR sigV = signalM / float(0x7fff) # calc. the RMS envelope in the time domain - rms0DbV, rms0_srate = audio_db_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs ) + rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs ) # locate the sample index of the peak of each note attack pkIdx0L = locate_peak_indexes( rms0DbV, rms0_srate, r['eventTimeL'] ) @@ -211,7 +239,7 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR # calc. the RMS envelope by taking the max spectral peak in each STFT window - rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_db_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, spectrumSmpIdx) + rmsDbV, rms_srate, specV, specHopIdx, binHz = audio_stft_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, spectrumSmpIdx) # specV[] is the spectrum of the note at spectrumSmpIdx @@ -222,12 +250,12 @@ def plot_spectral_ranges( inDir, pitchL, rmsWndMs=300, rmsHopMs=30, harmN=5, dbR def do_td_plot( inDir ): - rmsWndMs = 300 rmsHopMs = 30 dbRefWndMs = 500 harmCandN = 5 harmN = 3 + minAttkDb = 5.0 seqFn = os.path.join( inDir, "seq.json") audioFn = os.path.join( inDir, "audio.wav") @@ -241,12 +269,17 @@ def do_td_plot( inDir ): srate, signalM = wavfile.read(audioFn) sigV = signalM / float(0x7fff) - rms0DbV, rms0_srate = audio_db_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs ) + rms0DbV, rms0_srate = audio_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs ) - rmsDbV, rms_srate, binHz = audio_harm_db_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, midiPitch, harmCandN, harmN ) + rmsDbV, rms_srate, binHz = audio_harm_rms( srate, sigV, rmsWndMs, rmsHopMs, dbRefWndMs, midiPitch, harmCandN, harmN ) pkIdxL = locate_peak_indexes( rmsDbV, rms_srate, r['eventTimeL'] ) - + + + min_pk_idx, max_pk_idx = find_min_max_peak_index( rmsDbV, pkIdxL, minAttkDb ) + + skipPkIdxL = find_skip_peaks( rmsDbV, pkIdxL, min_pk_idx, max_pk_idx ) + fig,ax = plt.subplots() fig.set_size_inches(18.5, 10.5, forward=True) @@ -255,13 +288,17 @@ def do_td_plot( inDir ): ax.plot( secV, rmsDbV ) ax.plot( np.arange(0,len(rms0DbV)) / rms0_srate, rms0DbV, color="black" ) - for begMs, endMs in r['eventTimeL']: + # print beg/end boundaries + for i,(begMs, endMs) in enumerate(r['eventTimeL']): ax.axvline( x=begMs/1000.0, color="green") ax.axvline( x=endMs/1000.0, color="red") + ax.text(begMs/1000.0, 20.0, str(i) ) - + # plot peak markers for i,pki in enumerate(pkIdxL): - ax.plot( [pki / rms_srate], [ rmsDbV[pki] ], marker='.', color="black") + marker = "o" if i==min_pk_idx or i==max_pk_idx else "." + color = "red" if i in skipPkIdxL else "black" + ax.plot( [pki / rms_srate], [ rmsDbV[pki] ], marker=marker, color=color) plt.show()