Documentation sur soundbox
This commit is contained in:
parent
093a341dd2
commit
7cbd48b9ad
22
generate.py
22
generate.py
|
@ -1,3 +1,4 @@
|
||||||
|
import numpy as np
|
||||||
import soundbox
|
import soundbox
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -10,13 +11,20 @@ Génère un morceau au synthétiseur dans le fichier [output].""")
|
||||||
output_file = sys.argv[1]
|
output_file = sys.argv[1]
|
||||||
|
|
||||||
|
|
||||||
def sine(dur, freq, value=1):
|
def sharp_sine(dur, freq, value=1):
|
||||||
return soundbox.envelope(
|
return soundbox.envelope(
|
||||||
attack=.01, decay=.2, release=.2,
|
attack=.005, decay=.1, release=.1,
|
||||||
signal=soundbox.sine(dur, freq, value))
|
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 = (
|
chords_l = (
|
||||||
(('do', 2),),
|
(('do', 2),),
|
||||||
|
@ -36,11 +44,11 @@ chords_r = (
|
||||||
(('fa', 3), ('la', 3), ('do', 4)),
|
(('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)):
|
for i in range(len(chords_l)):
|
||||||
soundbox.add_signal(signal, start=i / 2 + shift,
|
soundbox.add_signal(signal, start=i / 2 + shift,
|
||||||
source=soundbox.chord(
|
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]),
|
freqs=soundbox.note_freqs(chords_l[i]),
|
||||||
value=.4
|
value=.4
|
||||||
))
|
))
|
||||||
|
@ -48,7 +56,7 @@ for shift in (.5, 4.5):
|
||||||
for i in range(len(chords_r)):
|
for i in range(len(chords_r)):
|
||||||
soundbox.add_signal(signal, start=i + shift,
|
soundbox.add_signal(signal, start=i + shift,
|
||||||
source=soundbox.chord(
|
source=soundbox.chord(
|
||||||
instr=sine, dur=1.1,
|
instr=sharp_sine, dur=0.9,
|
||||||
freqs=soundbox.note_freqs(chords_r[i]),
|
freqs=soundbox.note_freqs(chords_r[i]),
|
||||||
value=.4
|
value=.4
|
||||||
))
|
))
|
||||||
|
|
123
soundbox.py
123
soundbox.py
|
@ -12,24 +12,57 @@ max_val = 2 ** (8 * samp_width - 1) - 1
|
||||||
samp_rate = 44100
|
samp_rate = 44100
|
||||||
|
|
||||||
|
|
||||||
def add_signal(dest, start, source):
|
|
||||||
dest[int(samp_rate * start):int(samp_rate * start) + len(source)] += source
|
|
||||||
|
|
||||||
|
|
||||||
def silence(dur):
|
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),))
|
return np.zeros((int(samp_rate * dur),))
|
||||||
|
|
||||||
|
|
||||||
def sine(dur, freq, value=1):
|
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))
|
x = np.arange(int(samp_rate * dur))
|
||||||
return value * max_val * np.sin(2 * np.pi * freq * x / samp_rate)
|
return value * max_val * np.sin(2 * np.pi * freq * x / samp_rate)
|
||||||
|
|
||||||
|
|
||||||
def envelope(attack, decay, release, signal):
|
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)
|
total = len(signal)
|
||||||
attack = int(attack * total)
|
attack = int(attack * samp_rate)
|
||||||
decay = int(decay * total)
|
decay = int(decay * samp_rate)
|
||||||
release = int(release * total)
|
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
|
sustain = total - attack - decay - release
|
||||||
|
|
||||||
return signal * np.concatenate((
|
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),
|
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 = {
|
notes = {
|
||||||
'do': 0, 'si#': 0,
|
'do': 0, 'si#': 0,
|
||||||
'do#': 1, 'reb': 1,
|
'do#': 1, 'reb': 1,
|
||||||
|
@ -56,25 +129,34 @@ notes = {
|
||||||
|
|
||||||
|
|
||||||
def note_freq(note, octave):
|
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
|
return (440
|
||||||
* (2 ** (octave - 3))
|
* (2 ** (octave - 3))
|
||||||
* math.pow(2, (notes[note] - 9) / 12))
|
* math.pow(2, (notes[note] - 9) / 12))
|
||||||
|
|
||||||
|
|
||||||
def note_freqs(notes):
|
def note_freqs(notes):
|
||||||
|
"""Calcule la fréquence correspondant à un ensemble de notes."""
|
||||||
return list(map(lambda info: note_freq(*info), 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):
|
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:
|
with wave.open(out_name, 'w') as file:
|
||||||
file.setnchannels(1)
|
file.setnchannels(1)
|
||||||
file.setsampwidth(samp_width)
|
file.setsampwidth(samp_width)
|
||||||
|
@ -83,6 +165,15 @@ def save_signal(out_name, signal):
|
||||||
|
|
||||||
|
|
||||||
def load_signal(in_name):
|
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:
|
with wave.open(in_name, 'r') as file:
|
||||||
assert file.getnchannels() == 1
|
assert file.getnchannels() == 1
|
||||||
assert file.getsampwidth() == samp_width
|
assert file.getsampwidth() == samp_width
|
||||||
|
|
Loading…
Reference in New Issue