diff --git a/fft-decode.py b/fft-decode.py new file mode 100644 index 0000000..465aa6c --- /dev/null +++ b/fft-decode.py @@ -0,0 +1,33 @@ +import soundbox +import numpy as np +import scipy.signal as sig +import sys + +if len(sys.argv) != 2: + print(f"""Utilisation: {sys.argv[0]} [source] + +Détermine les notes jouées dans le fichier [source] en utilisant +la transformation de Fourier.""") + sys.exit(1) + +source_file = sys.argv[1] + +# Calcul du FFT +signal = soundbox.load_signal(source_file) +freq_scale = soundbox.samp_rate / len(signal) + +vecs = np.fft.fft(signal)[:soundbox.samp_rate // 2] +values = np.absolute(vecs) / np.max(np.absolute(vecs)) +freq = np.arange(len(values)) + +# Calcul de la fenêtre des fréquences intéressantes +high_enough = np.where(values >= 0.005) +left = high_enough[0][0] +right = high_enough[0][-1] + +freq = freq[left:right] +values = values[left:right] + +# Recherche des pics +peaks, _ = sig.find_peaks(values, height=0.1, distance=10 / freq_scale) +print(list(map(soundbox.freq_note, freq[peaks] * freq_scale))) diff --git a/fft-graph.py b/fft-graph.py index 170bf64..360b20c 100644 --- a/fft-graph.py +++ b/fft-graph.py @@ -17,8 +17,19 @@ output_file = sys.argv[2] # Calcul du FFT signal = soundbox.load_signal(source_file) +freq_scale = soundbox.samp_rate / len(signal) + vecs = np.fft.fft(signal)[:soundbox.samp_rate // 2] values = np.absolute(vecs) / np.max(np.absolute(vecs)) +freq = np.arange(len(values)) + +# Calcul de la fenêtre des fréquences intéressantes +high_enough = np.where(values >= 0.005) +left = high_enough[0][0] +right = high_enough[0][-1] + +freq = freq[left:right] +values = values[left:right] # Génération du graphe plt.style.use('ggplot') @@ -30,15 +41,9 @@ plt.rcParams.update({ fig, ax = plt.subplots() ax.tick_params(axis='both', which='major', labelsize=12) - -freq_filter = values >= 0.01 -freq = np.arange(len(values)) -ax.plot(freq[freq_filter], values[freq_filter]) +ax.plot(freq, values) # Configuration des axes -freq_scale = soundbox.samp_rate / len(signal) - - def freq_format(value, pos): return f'{value * freq_scale:.0f} Hz' diff --git a/soundbox.py b/soundbox.py index 9e3fd5b..8930b84 100644 --- a/soundbox.py +++ b/soundbox.py @@ -113,20 +113,21 @@ def chord(instr, dur, freqs, value=1): return signal notes = { - 'do': 0, 'si#': 0, - 'do#': 1, 'reb': 1, + 'si#': 0, 'do': 0, + 'reb': 1, 'do#': 1, 're': 2, - 're#': 3, 'mib': 3, - 'mi': 4, 'fab': 4, - 'fa': 5, 'mi#': 5, - 'fa#': 6, 'solb': 6, + 'mib': 3, 're#': 3, + 'fab': 4, 'mi': 4, + 'mi#': 5, 'fa': 5, + 'solb': 6, 'fa#': 6, 'sol': 7, - 'sol#': 8, 'lab': 8, + 'lab': 8, 'sol#': 8, 'la': 9, - 'la#': 10, 'sib': 10, - 'si': 11, 'dob': 11, + 'sib': 10, 'la#': 10, + 'dob': 11, 'si': 11, } +rev_notes = dict(zip(*reversed(list(zip(*notes.items()))))) def note_freq(note, octave): """ @@ -144,6 +145,24 @@ def note_freq(note, octave): * math.pow(2, (notes[note] - 9) / 12)) +def freq_note(freq): + """ + Retrouve la note correspondant au mieux à une fréquence. + + Paramètres: + note + """ + log = math.log2(freq / note_freq('do', 3)) + octave = math.floor(log) + 3 + note = round(12 * log - 12 * math.floor(log)) + + if note == 12: + octave += 1 + note = 0 + + return (rev_notes[note], octave) + + def note_freqs(notes): """Calcule la fréquence correspondant à un ensemble de notes.""" return list(map(lambda info: note_freq(*info), notes)) diff --git a/sounds/synth.wav b/sounds/synth.wav index e03bd48..42d29b3 100644 Binary files a/sounds/synth.wav and b/sounds/synth.wav differ