From 7cbd48b9add8a2263f4baf3f0e482a4bc409cd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Sat, 19 Dec 2020 21:04:56 +0100 Subject: [PATCH] Documentation sur soundbox --- generate.py | 22 +++++++--- soundbox.py | 123 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 122 insertions(+), 23 deletions(-) diff --git a/generate.py b/generate.py index 3047629..afb787f 100644 --- a/generate.py +++ b/generate.py @@ -1,3 +1,4 @@ +import numpy as np import soundbox import sys @@ -10,13 +11,20 @@ Génère un morceau au synthétiseur dans le fichier [output].""") output_file = sys.argv[1] -def sine(dur, freq, value=1): +def sharp_sine(dur, freq, value=1): return soundbox.envelope( - attack=.01, decay=.2, release=.2, - signal=soundbox.sine(dur, freq, value)) + attack=.005, decay=.1, release=.1, + signal=soundbox.sine(dur + .2, freq, value)) -signal = soundbox.silence(10) +def smooth_sine(dur, freq, value=1): + return soundbox.envelope( + attack=.1, decay=.2, release=.1, + signal=soundbox.sine(dur + .2, freq, value)) + + +length = 9 +signal = soundbox.silence(length) chords_l = ( (('do', 2),), @@ -36,11 +44,11 @@ chords_r = ( (('fa', 3), ('la', 3), ('do', 4)), ) -for shift in (.5, 4.5): +for shift in np.arange(.5, length - 4, 4): for i in range(len(chords_l)): soundbox.add_signal(signal, start=i / 2 + shift, source=soundbox.chord( - instr=sine, dur=.8 if shift == 4.5 and i == 7 else .4, + instr=smooth_sine, dur=.6 if shift == 4.5 and i == 7 else .4, freqs=soundbox.note_freqs(chords_l[i]), value=.4 )) @@ -48,7 +56,7 @@ for shift in (.5, 4.5): for i in range(len(chords_r)): soundbox.add_signal(signal, start=i + shift, source=soundbox.chord( - instr=sine, dur=1.1, + instr=sharp_sine, dur=0.9, freqs=soundbox.note_freqs(chords_r[i]), value=.4 )) diff --git a/soundbox.py b/soundbox.py index b3a04ce..9e3fd5b 100644 --- a/soundbox.py +++ b/soundbox.py @@ -12,24 +12,57 @@ max_val = 2 ** (8 * samp_width - 1) - 1 samp_rate = 44100 -def add_signal(dest, start, source): - dest[int(samp_rate * start):int(samp_rate * start) + len(source)] += source - - def silence(dur): + """ + Génère un signal silencieux. + + Paramètres: + dur (float): Durée en secondes + + Retourne: + (ndarray): Signal généré + """ return np.zeros((int(samp_rate * dur),)) def sine(dur, freq, value=1): + """ + Génère un signal sinusoïdal. + + Paramètres: + dur (float): Durée en secondes + freq (float): Fréquence de la sinusoïde en hertz + value (float): Amplitude du signal (valeur relative entre 0 et 1) + + Retourne: + (ndarray): Signal généré + """ x = np.arange(int(samp_rate * dur)) return value * max_val * np.sin(2 * np.pi * freq * x / samp_rate) def envelope(attack, decay, release, signal): + """ + Applique une enveloppe à une note. + + Paramètres: + attack (float): Temps d’attaque de la note (en secondes) + decay (float): Temps de chute de la note vers la phase + de maintien (en secondes) + release (float): Temps de relâche à la fin de la note (en secondes) + signal (ndarray): Signal original + + Retourne: + (ndarray): Signal généré + """ total = len(signal) - attack = int(attack * total) - decay = int(decay * total) - release = int(release * total) + attack = int(attack * samp_rate) + decay = int(decay * samp_rate) + release = int(release * samp_rate) + + if attack + decay + release > total: + raise ValueError('Note trop courte pour l’application de l’enveloppe') + sustain = total - attack - decay - release return signal * np.concatenate(( @@ -39,6 +72,46 @@ def envelope(attack, decay, release, signal): np.linspace(start=2/3, stop=0, num=release, endpoint=True), )) + +def add_signal(dest, start, source): + """ + Ajoute un signal source dans un autre signal. + + Paramètres: + dest (ndarray): Signal dans lequel le signal source sera ajouté + start (float): Temps en secondes à partir duquel le signal est ajouté + source (ndarray): Signal source + + Retourne: None + """ + dest[int(samp_rate * start):int(samp_rate * start) + len(source)] += source + + +def chord(instr, dur, freqs, value=1): + """ + Construit un accord de notes. + + Paramètres: + instr (function): Instrument à utiliser pour générer les notes + dur (float): Durée de chaque note (en secondes) + freqs (list): Fréquence de chaque note + value (float): Amplitude totale partagée par les notes + + Retourne: + (ndarray): Signal généré + """ + signal = np.ndarray(0) + + for freq in freqs: + new_signal = instr(dur, freq, value / len(freqs)) + + if len(new_signal) > len(signal): + signal.resize(len(new_signal)) + + signal += new_signal + + return signal + notes = { 'do': 0, 'si#': 0, 'do#': 1, 'reb': 1, @@ -56,25 +129,34 @@ notes = { def note_freq(note, octave): + """ + Calcule la fréquence correspondant à une note. + + Paramètres: + note (str): Nom de la note + octave (int): Numéro de l’octave + + Retourne: + (float): Fréquence correspondante en hertz + """ return (440 * (2 ** (octave - 3)) * math.pow(2, (notes[note] - 9) / 12)) def note_freqs(notes): + """Calcule la fréquence correspondant à un ensemble de 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): + """ + Écrit un signal dans un fichier au format WAV. + + Paramètres: + out_name (str): Chemin vers le fichier + signal (ndarray): Signal à enregistrer + """ with wave.open(out_name, 'w') as file: file.setnchannels(1) file.setsampwidth(samp_width) @@ -83,6 +165,15 @@ def save_signal(out_name, signal): def load_signal(in_name): + """ + Charge un signal depuis un fichier au format WAV. + + Paramètres: + in_name (str): Chemin vers le fichier + + Retourne: + (ndarray): Signal décodé + """ with wave.open(in_name, 'r') as file: assert file.getnchannels() == 1 assert file.getsampwidth() == samp_width