Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synthio bugfixes #7969

Merged
merged 4 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions shared-module/synthio/Note.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) {
void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_tremolo_depth);
self->tremolo_descr.amplitude = val;
self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32767);
self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32768);
}

mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self) {
Expand All @@ -96,7 +96,7 @@ void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_tremolo_rate);
self->tremolo_descr.frequency = val;
if (self->sample_rate != 0) {
self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate);
self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
}
}

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

mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) {
Expand All @@ -127,7 +127,7 @@ void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_bend_rate);
self->bend_descr.frequency = val;
if (self->sample_rate != 0) {
self->bend_state.dds = synthio_frequency_convert_float_to_dds(val, self->sample_rate);
self->bend_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
}
}

Expand Down
3 changes: 2 additions & 1 deletion shared-module/synthio/Synthesizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_ob
mp_obj_t iterable = mp_getiter(to_press, &iter_buf);
mp_obj_t note_obj;
while ((note_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
note_obj = validate_note(note_obj);
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
synthio_note_start(note, self->synth.sample_rate);
}
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, validate_note(note_obj));
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note_obj);
}
}

Expand Down
10 changes: 6 additions & 4 deletions shared-module/synthio/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,19 +314,21 @@ STATIC void run_fir(synthio_synth_t *synth, int32_t *out_buffer32, uint16_t dur)
size_t fir_len = synth->filter_bufinfo.len / sizeof(int16_t);
int32_t *in_buf = synth->filter_buffer;


int synth_chan = synth->channel_count;
// FIR and copy values to output buffer
for (int16_t i = 0; i < dur; i++) {
for (int16_t i = 0; i < dur * synth_chan; i++) {
int32_t acc = 0;
for (size_t j = 0; j < fir_len; j++) {
// shift 5 here is good for up to 32 filtered voices, else might wrap
acc = acc + (in_buf[j] * (coeff[j] >> 5));
acc = acc + (in_buf[j * synth_chan] * (coeff[j] >> 5));
}
*out_buffer32++ = acc >> 10;
in_buf++;
}

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

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

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

for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
Expand Down
282 changes: 282 additions & 0 deletions tests/circuitpython-manual/synthio/note/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave

h = np.array(
[
-0.001229734800309099,
-0.008235561806605458,
-0.015082497016061390,
-0.020940136918319988,
-0.024981800822463429,
-0.026464233332370746,
-0.024803890156806906,
-0.019642276775473012,
-0.010893620860173042,
0.001230341899766145,
0.016221637398855598,
0.033304135659230648,
0.051486665261155681,
0.069636961761409016,
0.086570197432542767,
0.101144354207918147,
0.112353938422488253,
0.119413577288191297,
0.121823886314051028,
0.119413577288191297,
0.112353938422488253,
0.101144354207918147,
0.086570197432542767,
0.069636961761409016,
0.051486665261155681,
0.033304135659230648,
0.016221637398855598,
0.001230341899766145,
-0.010893620860173042,
-0.019642276775473012,
-0.024803890156806906,
-0.026464233332370746,
-0.024981800822463429,
-0.020940136918319988,
-0.015082497016061390,
-0.008235561806605458,
-0.001229734800309099,
]
)

filter_coeffs = np.array(h[::-1] * 32768, dtype=np.int16)


def randf(lo, hi):
return random.random() * (hi - lo) + lo


SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, dtype=np.int16
)
square = np.array([24000] * (SAMPLE_SIZE // 2) + [-24000] * (SAMPLE_SIZE // 2), dtype=np.int16)

noise = np.array(
[random.randint(-32768, 32767) for i in range(SAMPLE_SIZE)],
dtype=np.int16,
)

envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8
)

instant = synthio.Envelope(
attack_time=0, decay_time=0, release_time=0, attack_level=1, sustain_level=1
)
synth = synthio.Synthesizer(
sample_rate=48000, envelope=None, filter=filter_coeffs, channel_count=2
)


def synthesize(synth):
print(
"""you can use arbitrary waveforms, including ones calculated on the fly or read from wave files (up to 1024 points)"""
)

waveform = np.zeros(SAMPLE_SIZE, dtype=np.int16)
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=waveform, envelope=envelope)
for n in (60, 64, 67, 70)
]
synth.press(chord)

for i in range(256):
ctrl = i / 255
waveform[:] = np.array(square * (1 - ctrl) + sine * ctrl, dtype=np.int16)
yield 4


def synthesize2(synth):
print("""Envelope controls how notes fade in or out""")

chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=instant)
for n in (60, 64, 67, 70)
]

for i in range(4):
for c in chord:
synth.release_all_then_press((c,))
yield 24
synth.release_all()

chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=envelope)
for n in (60, 64, 67, 70)
]

for i in range(4):
for c in chord:
old = (c,)
synth.release_all_then_press((c,))
yield 24


def synthesize3(synth):
print("""A noise waveform creates percussive sounds""")

env = synthio.Envelope(
attack_time=0,
decay_time=0.2,
sustain_level=0,
)

notes = [
synthio.Note(
frequency=synthio.midi_to_hz(1 + i),
waveform=noise,
envelope=env,
)
for i in range(12)
]

random.seed(9)
for _ in range(16):
n = random.choice(notes)
d = random.randint(30, 60)
synth.press((n,))
yield d


def synthesize4(synth):
print("""Tremolo varies the note volume within a range at a low frequency""")

chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=None) for n in (60, 64, 67, 70)
]

synth.press(chord)
for i in range(16):
for c in chord:
c.tremolo_depth = i / 50
c.tremolo_rate = (i + 1) / 4
yield 48
yield 36


def synthesize5(synth):
print("""You can add vibrato or frequency sweep to notes""")

chord = [synthio.Note(synthio.midi_to_hz(n), waveform=sine) for n in (60, 64, 67, 70)]

synth.press(chord)
for i in range(16):
for c in chord:
c.bend_depth = 1 / 24
c.bend_rate = (i + 1) / 2
yield 24
synth.release_all()
yield 100

for c in chord:
synth.release_all()
c.bend_mode = synthio.BendMode.SWEEP_IN
c.bend_depth = randf(-1, 1)
c.bend_rate = 1 / 2
synth.press(chord)
yield 320


def synthesize6(synth):
print("""Ring modulation multiplies two waveforms together to create rich sounds""")

chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=square, ring_waveform=sine, envelope=envelope)
for n in (60,)
]

synth.press(chord)
yield 200

random.seed(75)

for _ in range(3):
synth.release_all()
yield 36
for note in chord:
note.ring_frequency = note.frequency * (random.random() * 35 / 1200 + 8)
synth.press(chord)
yield 200


def synthesize7(synth):
print("""FIR filtering can reproduce low, high, notch and band filters""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=square, filter=False)
for n in (60, 64, 67, 70)
]

for i in range(4):
for c in chord:
synth.release_all_then_press((c,))
yield 24
synth.release_all()

for note in chord:
note.filter = True

for i in range(4):
for c in chord:
synth.release_all_then_press((c,))
yield 24


def synthesize8(synth):
print("""Notes can be panned between channels""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=square, envelope=envelope)
for n in (60, 64, 67, 70)
]
synth.press(chord)
for p in range(-10, 11, 1):
for note in chord:
note.panning = p / 10
yield 36


def delay(synth):
synth.release_all()
yield 200


def chain(*args):
for a in args:
yield from a


# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("demo.wav", "w") as f:
f.setnchannels(2)
f.setsampwidth(2)
f.setframerate(48000)
import array

for n in chain(
synthesize(synth),
delay(synth),
synthesize2(synth),
delay(synth),
synthesize3(synth),
delay(synth),
synthesize4(synth),
delay(synth),
synthesize5(synth),
delay(synth),
synthesize6(synth),
delay(synth),
synthesize7(synth),
delay(synth),
synthesize8(synth),
):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)