|
|
@ -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 |
|
|
|
|
|
|
|
def silence(dur): |
|
|
|
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 |
|
|
|