<jupyter_start><jupyter_text>Meter estimation on symbolic dataMeter, time signature, beat, downbeat, and tempo estimation form a group of related tasks. At first glance, many of them might seem trivial on symbolic music, after all the precise onsets of notes are known from the MIDI data. But with changing tempi and unknown measures it can be quite challenging.<jupyter_code>import partitura as pt
import os
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
path_to_midi = os.path.join("..","introduction","example_data","Chopin_op10_no3_p01.mid")
ppart = pt.load_performance_midi(path_to_midi)
note_array = ppart.note_array
# let's have a look at the distribution of inter onset intervals

IOIs = np.diff(np.sort(note_array["onset_sec"]))
plt.hist(IOIs, bins = 100)
# convert the measured onsets to a framed unidimensional signal that 
# contains the number of onsets in a performance in a small window in time, e.g. 20 ms 

FRAMERATE = 50

onset_min = note_array["onset_sec"].min()
onset_max = note_array["onset_sec"].max()

onset_duration = onset_max - onset_min
frames = np.zeros(int(onset_duration*FRAMERATE)+1)
for note in note_array:
    frames[int((note["onset_sec"]-onset_min)*FRAMERATE)] += 1
    
plt.plot(frames)
# a different frame generation process that aggregates very close onsets and encodes dynamics / quantity

aggregated_notes = [(0,0)]
CHORD_SPREAD_TIME = 1/12 # a little faster than 16th notes at 180 bpm
for note in ppart.notes:
    prev_note_on = aggregated_notes[-1][0]
    note_on = note["note_on"]
    prev_note_vel = aggregated_notes[-1][1]
    if abs(note_on - prev_note_on) < CHORD_SPREAD_TIME:
        aggregated_notes[-1] = (note_on, prev_note_vel + 1) #note["velocity"]
        
    else:
        aggregated_notes.append((note_on, 1))
    
frames_a = np.zeros(int(onset_duration*FRAMERATE)+1)
for note in aggregated_notes:
    frames_a[int((note[0]-onset_min)*FRAMERATE)] += note[1]
    
frames_a[frames_a < 1.1] = 0.0
    
plt.plot(frames_a[:600])
# for possible tempi compute autocorrelation with increasing lag parameters

autocorr = np.zeros((4*FRAMERATE,frames.shape[0])  )

for framediff in range(1, 4*FRAMERATE+1):
    autocorr[framediff-1,framediff:] = frames_a[framediff:]*frames_a[:-framediff]
plt.plot(np.sum(autocorr, axis = 1))
# how to interpret these dimensions?
# the number of frames between onsets is inversely proportional to the tempo of the onsets!
from scipy.signal import find_peaks
# see https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks.html
a = find_peaks(np.sum(autocorr, axis = 1), prominence = 50)
m = np.argmax(np.sum(autocorr, axis = 1))
print(FRAMERATE/(m+1)*60)
print(a)
HOPSIZE = 20
WINDOWSIZE = 200
number_of_windows = int((autocorr.shape[1]-WINDOWSIZE)/HOPSIZE)

local_autocorr = np.zeros((autocorr.shape[0],number_of_windows))

for k in range(number_of_windows):
    local_autocorr[:,k] = np.sum(autocorr[:,k*HOPSIZE:k*HOPSIZE+WINDOWSIZE]
, axis = 1)    
plt.imshow(local_autocorr*10, aspect="auto")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11220472440944881, 'fraction_numerical': 0.021325459317585303, 'mean_word_length': 5.247950819672131, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '200841', 'license': 'MIT', 'n_tokens_mistral': 1147, 'n_tokens_neox': 1063, 'n_words': 275, 'path': 'key_meter_estimation/meter_estimation.ipynb', 'repo_name': 'MusicalInformatics/miws2021env', 'size': '3048'}