Skip to content

Commit 91a5103

Browse files
committed
synthio: add a host demo of all major features
1 parent f68ab9c commit 91a5103

File tree

1 file changed

+282
-0
lines changed
  • tests/circuitpython-manual/synthio/note

1 file changed

+282
-0
lines changed
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
import random
2+
import audiocore
3+
import synthio
4+
from ulab import numpy as np
5+
import adafruit_wave as wave
6+
7+
h = np.array(
8+
[
9+
-0.001229734800309099,
10+
-0.008235561806605458,
11+
-0.015082497016061390,
12+
-0.020940136918319988,
13+
-0.024981800822463429,
14+
-0.026464233332370746,
15+
-0.024803890156806906,
16+
-0.019642276775473012,
17+
-0.010893620860173042,
18+
0.001230341899766145,
19+
0.016221637398855598,
20+
0.033304135659230648,
21+
0.051486665261155681,
22+
0.069636961761409016,
23+
0.086570197432542767,
24+
0.101144354207918147,
25+
0.112353938422488253,
26+
0.119413577288191297,
27+
0.121823886314051028,
28+
0.119413577288191297,
29+
0.112353938422488253,
30+
0.101144354207918147,
31+
0.086570197432542767,
32+
0.069636961761409016,
33+
0.051486665261155681,
34+
0.033304135659230648,
35+
0.016221637398855598,
36+
0.001230341899766145,
37+
-0.010893620860173042,
38+
-0.019642276775473012,
39+
-0.024803890156806906,
40+
-0.026464233332370746,
41+
-0.024981800822463429,
42+
-0.020940136918319988,
43+
-0.015082497016061390,
44+
-0.008235561806605458,
45+
-0.001229734800309099,
46+
]
47+
)
48+
49+
filter_coeffs = np.array(h[::-1] * 32768, dtype=np.int16)
50+
51+
52+
def randf(lo, hi):
53+
return random.random() * (hi - lo) + lo
54+
55+
56+
SAMPLE_SIZE = 1024
57+
VOLUME = 14700
58+
sine = np.array(
59+
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, dtype=np.int16
60+
)
61+
square = np.array([24000] * (SAMPLE_SIZE // 2) + [-24000] * (SAMPLE_SIZE // 2), dtype=np.int16)
62+
63+
noise = np.array(
64+
[random.randint(-32768, 32767) for i in range(SAMPLE_SIZE)],
65+
dtype=np.int16,
66+
)
67+
68+
envelope = synthio.Envelope(
69+
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8
70+
)
71+
72+
instant = synthio.Envelope(
73+
attack_time=0, decay_time=0, release_time=0, attack_level=1, sustain_level=1
74+
)
75+
synth = synthio.Synthesizer(
76+
sample_rate=48000, envelope=None, filter=filter_coeffs, channel_count=2
77+
)
78+
79+
80+
def synthesize(synth):
81+
print(
82+
"""you can use arbitrary waveforms, including ones calculated on the fly or read from wave files (up to 1024 points)"""
83+
)
84+
85+
waveform = np.zeros(SAMPLE_SIZE, dtype=np.int16)
86+
chord = [
87+
synthio.Note(synthio.midi_to_hz(n), waveform=waveform, envelope=envelope)
88+
for n in (60, 64, 67, 70)
89+
]
90+
synth.press(chord)
91+
92+
for i in range(256):
93+
ctrl = i / 255
94+
waveform[:] = np.array(square * (1 - ctrl) + sine * ctrl, dtype=np.int16)
95+
yield 4
96+
97+
98+
def synthesize2(synth):
99+
print("""Envelope controls how notes fade in or out""")
100+
101+
chord = [
102+
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=instant)
103+
for n in (60, 64, 67, 70)
104+
]
105+
106+
for i in range(4):
107+
for c in chord:
108+
synth.release_all_then_press((c,))
109+
yield 24
110+
synth.release_all()
111+
112+
chord = [
113+
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=envelope)
114+
for n in (60, 64, 67, 70)
115+
]
116+
117+
for i in range(4):
118+
for c in chord:
119+
old = (c,)
120+
synth.release_all_then_press((c,))
121+
yield 24
122+
123+
124+
def synthesize3(synth):
125+
print("""A noise waveform creates percussive sounds""")
126+
127+
env = synthio.Envelope(
128+
attack_time=0,
129+
decay_time=0.2,
130+
sustain_level=0,
131+
)
132+
133+
notes = [
134+
synthio.Note(
135+
frequency=synthio.midi_to_hz(1 + i),
136+
waveform=noise,
137+
envelope=env,
138+
)
139+
for i in range(12)
140+
]
141+
142+
random.seed(9)
143+
for _ in range(16):
144+
n = random.choice(notes)
145+
d = random.randint(30, 60)
146+
synth.press((n,))
147+
yield d
148+
149+
150+
def synthesize4(synth):
151+
print("""Tremolo varies the note volume within a range at a low frequency""")
152+
153+
chord = [
154+
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=None) for n in (60, 64, 67, 70)
155+
]
156+
157+
synth.press(chord)
158+
for i in range(16):
159+
for c in chord:
160+
c.tremolo_depth = i / 50
161+
c.tremolo_rate = (i + 1) / 4
162+
yield 48
163+
yield 36
164+
165+
166+
def synthesize5(synth):
167+
print("""You can add vibrato or frequency sweep to notes""")
168+
169+
chord = [synthio.Note(synthio.midi_to_hz(n), waveform=sine) for n in (60, 64, 67, 70)]
170+
171+
synth.press(chord)
172+
for i in range(16):
173+
for c in chord:
174+
c.bend_depth = 1 / 24
175+
c.bend_rate = (i + 1) / 2
176+
yield 24
177+
synth.release_all()
178+
yield 100
179+
180+
for c in chord:
181+
synth.release_all()
182+
c.bend_mode = synthio.BendMode.SWEEP_IN
183+
c.bend_depth = randf(-1, 1)
184+
c.bend_rate = 1 / 2
185+
synth.press(chord)
186+
yield 320
187+
188+
189+
def synthesize6(synth):
190+
print("""Ring modulation multiplies two waveforms together to create rich sounds""")
191+
192+
chord = [
193+
synthio.Note(synthio.midi_to_hz(n), waveform=square, ring_waveform=sine, envelope=envelope)
194+
for n in (60,)
195+
]
196+
197+
synth.press(chord)
198+
yield 200
199+
200+
random.seed(75)
201+
202+
for _ in range(3):
203+
synth.release_all()
204+
yield 36
205+
for note in chord:
206+
note.ring_frequency = note.frequency * (random.random() * 35 / 1200 + 8)
207+
synth.press(chord)
208+
yield 200
209+
210+
211+
def synthesize7(synth):
212+
print("""FIR filtering can reproduce low, high, notch and band filters""")
213+
chord = [
214+
synthio.Note(synthio.midi_to_hz(n), waveform=square, filter=False)
215+
for n in (60, 64, 67, 70)
216+
]
217+
218+
for i in range(4):
219+
for c in chord:
220+
synth.release_all_then_press((c,))
221+
yield 24
222+
synth.release_all()
223+
224+
for note in chord:
225+
note.filter = True
226+
227+
for i in range(4):
228+
for c in chord:
229+
synth.release_all_then_press((c,))
230+
yield 24
231+
232+
233+
def synthesize8(synth):
234+
print("""Notes can be panned between channels""")
235+
chord = [
236+
synthio.Note(synthio.midi_to_hz(n), waveform=square, envelope=envelope)
237+
for n in (60, 64, 67, 70)
238+
]
239+
synth.press(chord)
240+
for p in range(-10, 11, 1):
241+
for note in chord:
242+
note.panning = p / 10
243+
yield 36
244+
245+
246+
def delay(synth):
247+
synth.release_all()
248+
yield 200
249+
250+
251+
def chain(*args):
252+
for a in args:
253+
yield from a
254+
255+
256+
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
257+
with wave.open("demo.wav", "w") as f:
258+
f.setnchannels(2)
259+
f.setsampwidth(2)
260+
f.setframerate(48000)
261+
import array
262+
263+
for n in chain(
264+
synthesize(synth),
265+
delay(synth),
266+
synthesize2(synth),
267+
delay(synth),
268+
synthesize3(synth),
269+
delay(synth),
270+
synthesize4(synth),
271+
delay(synth),
272+
synthesize5(synth),
273+
delay(synth),
274+
synthesize6(synth),
275+
delay(synth),
276+
synthesize7(synth),
277+
delay(synth),
278+
synthesize8(synth),
279+
):
280+
for i in range(n):
281+
result, data = audiocore.get_buffer(synth)
282+
f.writeframes(data)

0 commit comments

Comments
 (0)