-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdo-synth.py
More file actions
166 lines (133 loc) · 3.76 KB
/
do-synth.py
File metadata and controls
166 lines (133 loc) · 3.76 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python
# File: do-synth.py
# Author: thylordroot
# Since: 6/19/2020
#
# This script is responsible for generating synthesizer samples
#
import os
import os.path
import math;
import wave;
import io;
import struct;
import random;
def normPath(x):
return x.replace('/', os.sep);
def ensureDir(x):
x = normPath(x);
if not os.path.isdir(x):
os.mkdir(x);
# Primitive Waveforms
def sin(t):
return math.sin(2*math.pi*t);
def sqr(t, duty=0.5):
phase = t % 1;
if phase <= duty:
return 1
else:
return -1
def saw(t):
phase = t % 1;
if (t % 1) <= 0.5:
return 2*phase
else:
return 2*(phase-1)
def tri(t):
phase = t % 1;
if phase <= 0.25:
return 4*phase
elif phase <= 0.75:
return 4*(0.5 - phase)
else:
return -1 + 4*(phase - 0.75);
def noise(t):
return random.uniform(-0.99, 0.99);
def sqra(t, harmonics=4):
smp = sin(t)
k = 3
while harmonics > 0:
smp = smp + 1/k * sin(k*t)
k = k + 2
harmonics = harmonics - 1
return smp
###################
# Derived Waveforms
###################
def _duty(func, t, c, cutout):
phase = t % 1
return func(phase) if phase <= c else cutout
# Wrap func so that it has a duty cycle of c
#
# This function takes func and cuts out after the c proportion of the period
# is over.
#
# Param: func The function to wrap
# Param: c A value from [0,1]
# Param: cutout The quiescent value when the duty cycle is over
def duty(func, c, cutout = 0):
return lambda t: _duty(func, t, c, cutout)
def clamp(x, hi = 1, lo=None):
if lo is None:
lo = -hi
if x > hi:
return hi
elif x < lo:
return lo
else:
return x
def distort(func, gain, hi = 1, lo=None):
return lambda t: clamp(gain* func(t), hi, lo)
##########
# Sampling
##########
# Create a sample buffer using func as a basis at the given frequency and
# bandwidth
#
# This function samples the waveform supplied by func at the specified
# frequency and bandwidth. The waveform generally has an amplitude of
# amp and will not exceed this value.
#
# Param: func The function to sample
# Param: amp The maximum amplitude of the function (default: 0.75)
# Param: freq The frequency at which to sample the function (default: 440)
# Param: cycles The number of times to repeat the function (default: 1)
# Param: bandwidth The sample rate at which to record the sample in
# samples per second. (default: 44100)
#
# Note: the actual frequency of the sample buffer may slightly different than
# what was requested and depends on the bandwidth. Typically, the sample
# generated will be slightly sharp. For instance, at 44.1KHz a 440Hz sample
# will end up being at 441Hz instead due to imprecise alignment alignment
# during the discretization process. On the other hand, 48KHz will lead to
# the resulting sample being tuned at 440.3Hz.
def sample(func, amp=0.75, freq=440, cycles=1, bandwidth=44100):
buf = list();
# Figure out how many samples we need in the buffer
period = bandwidth/freq;
for i in range(0, math.floor(cycles*period)):
t = i/period
buf.append(clamp(amp*func(t), amp));
return buf;
def capture(file, func, amp=0.75, freq=440, cycles=1, bandwidth=44100,
channels=1):
# Create our sample buffer
smp = sample(func, amp, freq, cycles, bandwidth)
with wave.open(normPath(file), mode="wb") as out:
out.setframerate(bandwidth)
out.setsampwidth(2)
out.setnchannels(channels)
out.setnframes(len(smp));
for i in smp:
v = struct.pack('<h', int(32767 * i));
out.writeframesraw(v);
ensureDir("smp/synth");
# Basic waveforms
capture("smp/synth/a-sin.wav", sin)
capture("smp/synth/a-sqr.wav", sqr)
capture("smp/synth/a-sqra.wav", sqra)
capture("smp/synth/a-saw.wav", saw)
capture("smp/synth/a-tri.wav", tri)
capture("smp/synth/a-noise.wav", noise, cycles=400)
# Duty Cycled waveforms
capture("smp/synth/a-sqr-25.wav", duty(sin, 0.25))