commit e8bfb981cca4d3feb3046eb92502179a51a54854 Author: Mattéo Delabre Date: Thu Dec 17 23:08:07 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..114e85e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out.wav diff --git a/analyze-shorttime.py b/analyze-shorttime.py new file mode 100644 index 0000000..88493fd --- /dev/null +++ b/analyze-shorttime.py @@ -0,0 +1,34 @@ +import soundbox +import numpy as np +import scipy.signal as sig +import matplotlib +import matplotlib.pyplot as plt + +signal = soundbox.load_signal('out.wav') + +freq, time, fts = sig.stft(signal, soundbox.samp_rate, nperseg=soundbox.samp_rate * 0.5) + +fig, ax = plt.subplots() +ax.pcolormesh( + time, freq, + np.abs(fts), + cmap='plasma', + shading='gouraud') + + +def freq_format(value, pos): + return f'{value:.0f} Hz' + + +def time_format(value, pos): + return f'{value:.0f} s' + + +ax.set_xlabel('Temps') +ax.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(time_format)) + +ax.set_ylabel('Fréquence') +ax.set_ylim(0, 800) +ax.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(freq_format)) + +plt.show() diff --git a/analyze-single.py b/analyze-single.py new file mode 100644 index 0000000..9635bb2 --- /dev/null +++ b/analyze-single.py @@ -0,0 +1,23 @@ +import soundbox +import numpy as np +import matplotlib +import matplotlib.pyplot as plt + +signal = soundbox.load_signal('out.wav') +freqs = np.fft.fft(signal) +scale = soundbox.samp_rate / len(signal) + +fig, ax = plt.subplots() +ax.plot(np.absolute(freqs)) + + +def freq_format(value, pos): + return f'{value * scale:.0f} Hz' + + +ax.set_xlabel('Fréquence') +ax.set_xlim(0 / scale, 800 / scale) +ax.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(freq_format)) +ax.xaxis.set_major_locator(plt.MultipleLocator(100 / scale)) + +plt.show() diff --git a/generate.py b/generate.py new file mode 100644 index 0000000..cdc79b2 --- /dev/null +++ b/generate.py @@ -0,0 +1,53 @@ +import soundbox + + +def sine(dur, freq, value=1): + return soundbox.envelope( + attack=.01, decay=.2, release=.2, + signal=soundbox.sine(dur, freq, value)) + + +def square(dur, freq, value=1): + return soundbox.envelope( + attack=.01, decay=.2, release=.2, + signal=soundbox.square(dur, freq, value)) + + +signal = soundbox.silence(9) + +chords_l = ( + (('do', 2),), + (('sol', 2),), + (('la', 2),), + (('sol', 2),), + (('do', 2),), + (('sol', 2),), + (('la', 2),), + (('sol', 2),), +) + +chords_r = ( + (('do', 3), ('mi', 3), ('sol', 3)), + (('sol', 3), ('si', 3), ('re', 4)), + (('la', 3), ('do', 4), ('mi', 4)), + (('fa', 3), ('la', 3), ('do', 4)), +) + +for shift in (.5, 4.5): + for i in range(len(chords_l)): + soundbox.add_signal(signal, start=i / 2 + shift, + source=soundbox.chord( + instr=square, dur=.6, + freqs=soundbox.note_freqs(chords_l[i]), + value=.05 + )) + + for i in range(len(chords_r)): + soundbox.add_signal(signal, start=i + shift, + source=soundbox.chord( + instr=sine, dur=1.1, + freqs=soundbox.note_freqs(chords_r[i]), + value=.6 + )) + +soundbox.save_signal('out.wav', signal) diff --git a/soundbox.py b/soundbox.py new file mode 100644 index 0000000..a55f624 --- /dev/null +++ b/soundbox.py @@ -0,0 +1,97 @@ +import wave +import numpy as np +import scipy.signal as sig +import math + +# Nombre d’octets par échantillon +samp_width = 2 + +# Valeur maximale d’un échantillon +max_val = 2 ** (8 * samp_width - 1) - 1 + +# Fréquence d’échantillonnage (Hertz) +samp_rate = 44100 + + +def add_signal(dest, start, source): + dest[int(samp_rate * start):int(samp_rate * start) + len(source)] += source + + +def silence(dur): + return np.zeros((int(samp_rate * dur),)) + + +def sine(dur, freq, value=1): + x = np.arange(int(samp_rate * dur)) + return value * max_val * np.sin(2 * np.pi * freq * x / samp_rate) + + +def square(dur, freq, value=1): + x = np.arange(int(samp_rate * dur)) + return value * max_val * sig.square(2 * np.pi * freq * x / samp_rate) + + +def envelope(attack, decay, release, signal): + total = len(signal) + attack = int(attack * total) + decay = int(decay * total) + release = int(release * total) + sustain = total - attack - decay - release + + return signal * np.concatenate(( + np.linspace(start=0, stop=1, num=attack, endpoint=False), + np.linspace(start=1, stop=2/3, num=decay, endpoint=False), + np.linspace(start=2/3, stop=2/3, num=sustain, endpoint=False), + np.linspace(start=2/3, stop=0, num=release, endpoint=True), + )) + +notes = { + 'do': 0, 'si#': 0, + 'do#': 1, 'reb': 1, + 're': 2, + 're#': 3, 'mib': 3, + 'mi': 4, 'fab': 4, + 'fa': 5, 'mi#': 5, + 'fa#': 6, 'solb': 6, + 'sol': 7, + 'sol#': 8, 'lab': 8, + 'la': 9, + 'la#': 10, 'sib': 10, + 'si': 11, 'dob': 11, +} + + +def note_freq(note, octave): + return (440 + * (2 ** (octave - 3)) + * math.pow(2, (notes[note] - 9) / 12)) + + +def note_freqs(notes): + return list(map(lambda info: note_freq(*info), notes)) + + +def chord(instr, dur, freqs, value=1): + signal = np.zeros((int(samp_rate * dur),)) + + for freq in freqs: + signal += instr(dur, freq, value / len(freqs)) + + return signal + + +def save_signal(out_name, signal): + with wave.open(out_name, 'w') as file: + file.setnchannels(1) + file.setsampwidth(samp_width) + file.setframerate(samp_rate) + file.writeframesraw(signal.astype('