import wave import numpy as np 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 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 * 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(( 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), )) 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 = { 'si#': 0, 'do': 0, 'reb': 1, 'do#': 1, 're': 2, 'mib': 3, 're#': 3, 'fab': 4, 'mi': 4, 'mi#': 5, 'fa': 5, 'solb': 6, 'fa#': 6, 'sol': 7, 'lab': 8, 'sol#': 8, 'la': 9, 'sib': 10, 'la#': 10, 'dob': 11, 'si': 11, } rev_notes = dict(zip(*reversed(list(zip(*notes.items()))))) 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 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)) 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) file.setframerate(samp_rate) file.writeframesraw(signal.astype('