music-analysis / src /pitch_estimation.py
Keycatowo
init trans commit
bb5feba
import librosa
from librosa import display
from librosa import feature
import numpy as np
from matplotlib import pyplot as plt
import scipy
from numpy import typing as npt
import typing
def plot_mel_spectrogram(
y: npt.ArrayLike,
sr:int,
shift_array: npt.ArrayLike,
with_pitch : bool = True,
):
S = librosa.feature.melspectrogram(y=y, sr=sr)
S_dB = librosa.power_to_db(S, ref=np.max)
if with_pitch :
f0, voiced_flag, voiced_probs = librosa.pyin(y,
fmin=librosa.note_to_hz('C2'),
fmax=librosa.note_to_hz('C7'))
times = librosa.times_like(f0, sr)
fig, ax = plt.subplots(figsize=(12,6))
img = librosa.display.specshow(S_dB, x_axis='time',
y_axis='mel', sr=sr,
fmax=8000, ax=ax)
ax.plot(times, f0, label='f0', color='cyan', linewidth=3)
ax.set_xticks(shift_array - shift_array[0],
shift_array)
fig.colorbar(img, ax=ax, format='%+2.0f dB')
ax.legend(loc='upper right')
ax.set(title='Mel-frequency spectrogram')
else :
fig, ax = plt.subplots(figsize=(12,6))
img = librosa.display.specshow(S_dB, x_axis='time',
y_axis='mel', sr=sr,
fmax=8000, ax=ax)
ax.set_xticks(shift_array - shift_array[0],
shift_array)
fig.colorbar(img, ax=ax, format='%+2.0f dB')
ax.set(title='Mel-frequency spectrogram')
ax.set_xlabel('Time (s)')
return fig, ax
def plot_constant_q_transform(y: npt.ArrayLike, sr:int,
shift_array: npt.ArrayLike
) :
C = np.abs(librosa.cqt(y, sr=sr))
fig, ax = plt.subplots(figsize=(12,6))
img = librosa.display.specshow(librosa.amplitude_to_db(C, ref=np.max),
sr=sr, x_axis='time', y_axis='cqt_note', ax=ax)
ax.set_xticks(shift_array - shift_array[0],
shift_array)
ax.set_title('Constant-Q power spectrum')
ax.set_xlabel('Time (s)')
fig.colorbar(img, ax=ax, format="%+2.0f dB")
return fig, ax
def pitch_class_type_one_vis(y: npt.ArrayLike, sr: int) -> None :
S = np.abs(librosa.stft(y))
chroma = librosa.feature.chroma_stft(S=S, sr=sr)
count_pitch = np.empty(np.shape(chroma)) # To count pitch
notes = np.array(librosa.key_to_notes('C:maj'))
# Set the threshold to determine the exact pitch
count_pitch[chroma < 0.5] = 0
count_pitch[chroma > 0.5] = 1
# To compute the probability
occurProbs = np.empty(np.shape(count_pitch)[0])
for i in range(np.shape(count_pitch)[0]) :
total = np.sum(count_pitch)
occurProbs[i] = np.sum(count_pitch[i]) / total
result = np.vstack((notes, np.round(occurProbs, 4))).T
ticks = range(12)
fig, ax = plt.subplots()
plt.title("Pitch Class")
plt.bar(ticks,occurProbs * 100, align='center')
plt.xticks(ticks, notes)
plt.xlabel("Note")
plt.ylabel("Number of occurrences %")
return fig, ax, result
def pitch_class_histogram_chroma(y: npt.ArrayLike, sr: int, higher_resolution: bool, save_to_csv: bool = False) -> None :
S = np.abs(librosa.stft(y))
notes = np.array(librosa.key_to_notes('C:maj')) # For x-axis legend
if not higher_resolution :
chroma = librosa.feature.chroma_stft(S=S, sr=sr)
valid_pitch = np.empty(np.shape(chroma)) # To count pitch
valid_pitch[chroma < 0.7] = 0
valid_pitch[chroma >= 0.7] = 1
total = np.sum(valid_pitch)
# To compute the probability
# WARNING: (12,) means pure 1-D array
occurProbs = np.empty((12,))
for i in range(0, 12) :
occurProbs[i] = np.sum(valid_pitch[i]) / total
ticks = range(12)
colors = ['lightcoral', 'goldenrod', 'lightseagreen', 'indigo', 'lightcoral',
'goldenrod', 'lightseagreen', 'indigo', 'lightcoral', 'goldenrod',
'lightseagreen', 'indigo']
xLegend = notes
fig, ax = plt.subplots()
ax.bar(ticks,occurProbs * 100, align='center', color=colors)
ax.set_xticks(ticks)
ax.set_xticklabels(xLegend)
ax.set_title("Pitch Class Histogram")
ax.set_xlabel("Note")
ax.set_ylabel("Occurrences %")
if higher_resolution :
chroma = librosa.feature.chroma_stft(S=S, sr=sr, n_chroma=120)
valid_pitch = np.empty(np.shape(chroma)) # To count pitch
valid_pitch[chroma < 0.7] = 0
valid_pitch[chroma >= 0.7] = 1
total = np.sum(valid_pitch)
occurProbs = np.empty((120,))
for i in range(0, 120) :
occurProbs[i] = np.sum(valid_pitch[i]) / total
ticks = range(120)
fig, ax = plt.subplots()
xLegend = list()
for i in range(120) :
if i % 10 == 0 :
xLegend.append(notes[i // 10])
else :
xLegend.append('')
colors = list()
for i in range(120) :
if i % 40 >=0 and i % 40 < 10 : colors.append('lightcoral')
elif i % 40 >= 10 and i % 40 < 20 : colors.append('goldenrod')
elif i % 40 >= 10 and i % 40 < 30 : colors.append('lightseagreen')
elif i % 40 >= 10 and i % 40 < 40 : colors.append('indigo')
fig, ax = plt.subplots()
ax.bar(ticks,occurProbs * 100, align='center', color = colors)
ax.set_xticks(ticks)
ax.set_xticklabels(xLegend)
ax.set_title("Pitch Class Histogram")
ax.set_xlabel("Note")
ax.set_ylabel("Occurrence %")
result = np.vstack((xLegend, np.round(occurProbs, 4))).T
if save_to_csv :
with open('pitch_class.csv', 'w') as out :
for row in result :
print(*row, sep=',', file=out)
return fig, ax, result