-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.py
144 lines (121 loc) · 5.34 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import joblib
import librosa
import numpy as np
import sys
import os
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
# When loading the .pkl files
chord_identifier_model_path = resource_path('chord_identifier.pkl')
label_encoder_path = resource_path('label_encoder.pkl')
class Chord_preprocessing():
def __init__(self):
pass
def normalize_audio_peak(self, audio, target_level=0.5):
peak = np.max(np.abs(audio))
if peak > 0:
audio = audio / peak * target_level
return audio
def adjust_loudness(self, audio, fs, target_rms=-15):
# Calculate the RMS energy of the audio
rms = np.sqrt(np.mean(audio**2))
# Calculate the gain needed to reach the target RMS level
gain = 10**((target_rms - 20 * np.log10(rms)) / 20)
# Apply the gain to the audio
adjusted_audio = audio * gain
# Specify the output filename
output_filename = "adjusted_audio.wav"
# Export the adjusted audio to a WAV file
#sf.write(output_filename, adjusted_audio, fs)
return adjusted_audio
class Chord_classifier():
'''Uses the chord_identifier.pkl model to classify any chord.'''
def __init__(self):
self.model = joblib.load('chord_identifier.pkl')
self.label_encoder = joblib.load('label_encoder.pkl')
self.preprocessing = Chord_preprocessing()
def get_notes_for_chord(self, chord):
'''takes a chord (C#) and gives you the triad notes in that chord'''
chord_notes_mapping = {
'Ab': ['Ab', 'Eb', 'C'],
'A': ['A', 'Db', 'E'],
'Am': ['A', 'C', 'E'],
'B': ['B', 'Gb', 'Eb'],
'Bb': ['Bb', 'D', 'F'],
'Bdim': ['B', 'D', 'F'],
'C': ['C', 'E', 'G'],
'Cm': ['C', 'Eb', 'G'],
'Db': ['Db', 'Ab', 'F'],
'Dbm': ['Db', 'E', 'Ab'],
'D': ['D', 'A', 'Gb'],
'Dm': ['D', 'F', 'A'],
'Eb': ['Eb', 'Bb', 'G'],
'E': ['E', 'B', 'Ab'],
'Em': ['E', 'G', 'B'],
'F': ['F', 'A', 'C'],
'Fm': ['F', 'Ab', 'C'],
'Gb': ['Gb', 'Db', 'Bb'],
'G': ['G', 'B', 'D'],
'Gm': ['G', 'Bb', 'D'],
'Bbm': ['Bb', 'Db', 'F'],
'Bm': ['B', 'D', 'Gb'],
'Gbm': ['Gb', 'Bb', 'Db']
# You can continue to add more chords and their notes as needed
}
if chord in chord_notes_mapping:
# Get the list of notes corresponding to the chord
chord_notes = chord_notes_mapping[chord]
# Return the first three notes from the list (or fewer if there are less than three)
return chord_notes[:3]
else:
# Handle the case when the chord is not in the dictionary
return []
def _extract_features(self, audio_file, fs):
audio = None
if type(audio_file) == str:
audio, fs = librosa.load(audio_file, sr = None)
else:
audio = audio_file
audio = self.preprocessing.normalize_audio_peak(audio)
#preprocessing
harmonic, percussive = librosa.effects.hpss(audio)
# Compute the constant-Q transform (CQT)
C = librosa.cqt(y=harmonic, sr=fs, fmin=librosa.note_to_hz('C1'), hop_length=256, n_bins=36)
# Convert the complex CQT output into magnitude, which represents the energy at each CQT bin
# Summing across the time axis gives us the aggregate energy for each pitch bin
pitch_sum = np.abs(C).sum(axis=1)
return pitch_sum
def predict_new_chord(self, audio_file_path, fs):
# Extract features from the new audio file
feature_vector = self._extract_features(audio_file_path, fs)
# # Reshape the feature vector to match the model's input shape
feature_vector = feature_vector.reshape(1, -1)
try:
predicted_label = self.model.predict(feature_vector)
predicted_chord = self.label_encoder.inverse_transform(predicted_label)
predicted_chord_notes = self.get_notes_for_chord(predicted_chord[0])
return predicted_chord_notes, predicted_chord
except Exception as e:
return "Error during prediction: %s", str(e)
def analyze_chord_progression(self, audio_file, buffer_length=1, hop_length=0.2):
# Load the audio file
y, sr = librosa.load(audio_file, sr=None)
# Calculate the number of samples per buffer
buffer_samples = int(buffer_length * sr)
hop_samples = int(hop_length * sr)
chords = []
# Start at the beginning and hop through the file
for start in range(0, len(y), hop_samples):
end = start + buffer_samples
# Make sure we don't go past the end of the audio file
if end <= len(y):
buffer = y[start:end]
# Predict the chord for this buffer
chord = self.predict_new_chord(buffer, sr)
chords.append(chord)
else:
break # We've reached the end of the audio
# Return the list of chords
return chords