Skip to content

Commit fff72ea

Browse files
authored
Merge pull request adafruit#7969 from jepler/synthio-bugfixes
Synthio bugfixes
2 parents 0a3faf8 + 91a5103 commit fff72ea

File tree

4 files changed

+294
-9
lines changed

4 files changed

+294
-9
lines changed

shared-module/synthio/Note.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) {
8585
void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value_in) {
8686
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_tremolo_depth);
8787
self->tremolo_descr.amplitude = val;
88-
self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32767);
88+
self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32768);
8989
}
9090

9191
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self) {
@@ -96,7 +96,7 @@ void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float
9696
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_tremolo_rate);
9797
self->tremolo_descr.frequency = val;
9898
if (self->sample_rate != 0) {
99-
self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate);
99+
self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
100100
}
101101
}
102102

@@ -107,7 +107,7 @@ mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self) {
107107
void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value_in) {
108108
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_bend_depth);
109109
self->bend_descr.amplitude = val;
110-
self->bend_state.amplitude_scaled = round_float_to_int(val * 32767);
110+
self->bend_state.amplitude_scaled = round_float_to_int(val * 32768);
111111
}
112112

113113
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) {
@@ -127,7 +127,7 @@ void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t
127127
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_bend_rate);
128128
self->bend_descr.frequency = val;
129129
if (self->sample_rate != 0) {
130-
self->bend_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate);
130+
self->bend_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
131131
}
132132
}
133133

shared-module/synthio/Synthesizer.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,12 @@ void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_ob
110110
mp_obj_t iterable = mp_getiter(to_press, &iter_buf);
111111
mp_obj_t note_obj;
112112
while ((note_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
113+
note_obj = validate_note(note_obj);
113114
if (!mp_obj_is_small_int(note_obj)) {
114115
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
115116
synthio_note_start(note, self->synth.sample_rate);
116117
}
117-
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, validate_note(note_obj));
118+
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note_obj);
118119
}
119120
}
120121

shared-module/synthio/__init__.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,19 +314,21 @@ STATIC void run_fir(synthio_synth_t *synth, int32_t *out_buffer32, uint16_t dur)
314314
size_t fir_len = synth->filter_bufinfo.len / sizeof(int16_t);
315315
int32_t *in_buf = synth->filter_buffer;
316316

317+
318+
int synth_chan = synth->channel_count;
317319
// FIR and copy values to output buffer
318-
for (int16_t i = 0; i < dur; i++) {
320+
for (int16_t i = 0; i < dur * synth_chan; i++) {
319321
int32_t acc = 0;
320322
for (size_t j = 0; j < fir_len; j++) {
321323
// shift 5 here is good for up to 32 filtered voices, else might wrap
322-
acc = acc + (in_buf[j] * (coeff[j] >> 5));
324+
acc = acc + (in_buf[j * synth_chan] * (coeff[j] >> 5));
323325
}
324326
*out_buffer32++ = acc >> 10;
325327
in_buf++;
326328
}
327329

328330
// Move values down so that they get filtered next time
329-
memmove(synth->filter_buffer, &synth->filter_buffer[dur], fir_len * sizeof(int32_t));
331+
memmove(synth->filter_buffer, &synth->filter_buffer[dur * synth_chan], fir_len * sizeof(int32_t) * synth_chan);
330332
}
331333

332334
STATIC bool synthio_synth_get_note_filtered(mp_obj_t note_obj) {
@@ -359,7 +361,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
359361

360362
if (synth->filter_buffer) {
361363
int32_t *filter_start = &synth->filter_buffer[synth->filter_bufinfo.len * synth->channel_count / sizeof(int16_t)];
362-
memset(filter_start, 0, dur * sizeof(int32_t));
364+
memset(filter_start, 0, dur * synth->channel_count * sizeof(int32_t));
363365

364366
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
365367
mp_obj_t note_obj = synth->span.note_obj[chan];
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy