Documentation sur soundbox

This commit is contained in:
Mattéo Delabre 2020-12-19 21:04:56 +01:00
parent 093a341dd2
commit 7cbd48b9ad
Signed by: matteo
GPG Key ID: AE3FBD02DC583ABB
2 changed files with 122 additions and 23 deletions

View File

@ -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
))

View File

@ -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 dattaque 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 lapplication de lenveloppe')
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 loctave
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