-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake_synth_sound.py
More file actions
87 lines (75 loc) · 2.19 KB
/
make_synth_sound.py
File metadata and controls
87 lines (75 loc) · 2.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import numpy as np
import scipy.signal
import soundfile as sf
from scipy.signal import butter, lfilter
import sounddevice as sd
# Shared defaults
DEFAULTS = {
"sample_rate": 44100,
"bpm": 140,
"beats": 4,
"base_freq": 499,
"attack_time": 0.3,
"release_time": 0.3,
"cutoff": 3000,
"delay_time": 1.0,
"delay_amount": 0.15,
"noise_level": 0.05,
}
def time_array(cfg):
beat_sec = 60.0 / cfg["bpm"]
dur = cfg["beats"] * beat_sec
return np.linspace(0, dur, int(cfg["sample_rate"] * dur), False)
def detuned_saw(cfg, t):
f = cfg["base_freq"]
return 0.5 * sum(
scipy.signal.sawtooth(2*np.pi*(f + d)*t)
for d in (0, 6)
)
def envelope(cfg, t, start_beat=0):
sr = cfg["sample_rate"]
atk = int(cfg["attack_time"] * sr)
rel = int(cfg["release_time"] * sr)
start = int(start_beat * len(t) / cfg["beats"])
env = np.zeros_like(t)
env[start:start+atk] = np.linspace(0, 1, atk)
env[start+atk:-rel] = 1
env[-rel:] = np.linspace(1, 0, rel)
return env
def lowpass(x, cfg):
b,a = butter(4, cfg["cutoff"]/(0.5*cfg["sample_rate"]), "low")
return lfilter(b,a,x)
def add_delay(x, cfg):
ds = int(cfg["delay_time"]*cfg["sample_rate"])
d = np.zeros_like(x)
if ds < len(x):
d[ds:] = cfg["delay_amount"] * x[:-ds]
return x + d
def normalize(x):
return x / np.max(np.abs(x))
def play_save(x, cfg, name):
sf.write(name, normalize(x), cfg["sample_rate"])
print(f"Saved {name}")
sd.play(x, cfg["sample_rate"])
sd.wait()
print("Done")
def make_pad(cfg, synth_start=0):
t = time_array(cfg)
noise = cfg["noise_level"] * np.random.randn(len(t))
saw = detuned_saw(cfg, t) if synth_start < cfg["beats"] else np.zeros_like(t)
env = envelope(cfg, t, start_beat=synth_start)
sig = noise + saw*env
sig = lowpass(sig, cfg)
sig = add_delay(sig, cfg)
return sig
def four_beats():
cfg = DEFAULTS.copy()
sig = make_pad(cfg, synth_start=0)
play_save(sig, cfg, "4beats_pad.wav")
def two_and_two():
cfg = DEFAULTS.copy()
# start synth on beat 2 of 4
sig = make_pad(cfg, synth_start=1)
play_save(sig, cfg, "2n2_pad.wav")
# run your choice
two_and_two()