Skip to content

Commit fff72ea

Browse files
authored
Merge pull request #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)