diff --git a/Synthio_Fundamentals/example_01_single_note.py b/Synthio_Fundamentals/example_01_single_note.py new file mode 100644 index 000000000..9d3e33bb3 --- /dev/null +++ b/Synthio_Fundamentals/example_01_single_note.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import synthio + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +synth = synthio.Synthesizer(sample_rate=44100) +audio.play(synth) + +while True: + synth.press(65) # midi note 65 = F4 + time.sleep(0.5) + synth.release(65) # release the note we pressed + time.sleep(2) diff --git a/Synthio_Fundamentals/example_02_chord.py b/Synthio_Fundamentals/example_02_chord.py new file mode 100644 index 000000000..7bb4f6469 --- /dev/null +++ b/Synthio_Fundamentals/example_02_chord.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import synthio + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +synth = synthio.Synthesizer(sample_rate=44100) +audio.play(synth) + +while True: + synth.press((65, 69, 72)) # midi note 65 = F4 + time.sleep(1) + synth.release((65, 69, 72)) # release the note we pressed + time.sleep(2) diff --git a/Synthio_Fundamentals/example_03_chord_mixer.py b/Synthio_Fundamentals/example_03_chord_mixer.py new file mode 100644 index 000000000..06bed3014 --- /dev/null +++ b/Synthio_Fundamentals/example_03_chord_mixer.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import audiomixer +import synthio + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=22050, buffer_size=2048) +synth = synthio.Synthesizer(channel_count=1, sample_rate=22050) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.4 + +while True: + synth.press((65, 69, 72)) # midi note 65 = F4 + time.sleep(0.5) + synth.release((65, 69, 72)) # release the note we pressed + time.sleep(0.5) + mixer.voice[0].level = (mixer.voice[0].level - 0.1) % 0.4 # reduce volume each pass diff --git a/Synthio_Fundamentals/example_04_envelope.py b/Synthio_Fundamentals/example_04_envelope.py new file mode 100644 index 000000000..05c6f346c --- /dev/null +++ b/Synthio_Fundamentals/example_04_envelope.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import audiomixer +import synthio + +# for PWM audio with an RC filter +#import audiopwmio +#audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=22050, buffer_size=2048) + +amp_env_slow = synthio.Envelope( + attack_time=0.2, + sustain_level=1.0, + release_time=0.8 +) + +amp_env_fast = synthio.Envelope( + attack_time=0.1, + sustain_level=0.5, + release_time=0.2 +) + + +synth = synthio.Synthesizer(channel_count=1, sample_rate=22050, envelope=amp_env_slow) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.2 + +while True: + synth.envelope = amp_env_slow + synth.press(46) + time.sleep(1.25) + synth.release(46) + time.sleep(1.25) + + synth.envelope = amp_env_fast + synth.press(51) + time.sleep(1.25) + synth.release(51) + time.sleep(1.25) diff --git a/Synthio_Fundamentals/example_05_filters.py b/Synthio_Fundamentals/example_05_filters.py new file mode 100644 index 000000000..cee343ba9 --- /dev/null +++ b/Synthio_Fundamentals/example_05_filters.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import audiomixer +import synthio + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=22050, buffer_size=2048) + +amp_env_slow = synthio.Envelope( + attack_time=0.2, + sustain_level=1.0, + release_time=0.8 +) + +synth = synthio.Synthesizer(channel_count=1, sample_rate=22050, envelope=amp_env_slow) + +# set up filters +frequency = 2000 +resonance = 1.5 +lpf = synth.low_pass_filter(frequency, resonance) +hpf = synth.high_pass_filter(frequency, resonance) +bpf = synth.band_pass_filter(frequency, resonance) + +note1 = synthio.Note(frequency=330, filter=None) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.2 + +while True: + # no filter + note1.filter = None + synth.press(note1) + time.sleep(1.25) + synth.release(note1) + time.sleep(1.25) + + # lpf + note1.filter = lpf + synth.press(note1) + time.sleep(1.25) + synth.release(note1) + time.sleep(1.25) + + # hpf + note1.filter = hpf + synth.press(note1) + time.sleep(1.25) + synth.release(note1) + time.sleep(1.25) + + # bpf + note1.filter = bpf + synth.press(note1) + time.sleep(1.25) + synth.release(note1) + time.sleep(1.25) diff --git a/Synthio_Fundamentals/example_06_MIDI_input.py b/Synthio_Fundamentals/example_06_MIDI_input.py new file mode 100644 index 000000000..39349e284 --- /dev/null +++ b/Synthio_Fundamentals/example_06_MIDI_input.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import board +import digitalio +import audiomixer +import synthio +import usb_midi +import adafruit_midi +from adafruit_midi.note_on import NoteOn +from adafruit_midi.note_off import NoteOff + +# note for ESP32-S2 boards, due to not enough available endpoints, +# to enable USB MIDI, create a "boot.py" with following in it, then power cycle board: +# import usb_hid +# import usb_midi +# usb_hid.disable() +# usb_midi.enable() +# print("enabled USB MIDI, disabled USB HID") + + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=22050, buffer_size=2048) + +midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0) + +amp_env_med = synthio.Envelope( + attack_time=0.05, + sustain_level=0.8, + release_time=0.2 +) + +synth = synthio.Synthesizer(channel_count=1, sample_rate=22050, envelope=amp_env_med) + +note1 = synthio.Note(frequency=330, filter=None) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.2 + +while True: + msg = midi.receive() + if isinstance(msg, NoteOn) and msg.velocity != 0: + print("noteOn: ", msg.note, "vel:", msg.velocity) + note1.frequency = synthio.midi_to_hz(msg.note) + synth.press(note1) + elif isinstance(msg, NoteOff) or isinstance(msg, NoteOn) and msg.velocity == 0: + print("noteOff:", msg.note, "vel:", msg.velocity) + note1.frequency = synthio.midi_to_hz(msg.note) + synth.release(note1) diff --git a/Synthio_Fundamentals/example_07_MIDI_input_CC.py b/Synthio_Fundamentals/example_07_MIDI_input_CC.py new file mode 100644 index 000000000..a6be236d1 --- /dev/null +++ b/Synthio_Fundamentals/example_07_MIDI_input_CC.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import board +import busio +import digitalio +import audiomixer +import synthio +import usb_midi +import adafruit_midi +from adafruit_midi.note_on import NoteOn +from adafruit_midi.note_off import NoteOff +from adafruit_midi.control_change import ControlChange + +# note for ESP32-S2 boards, due to not enough available endpoints, +# to enable USB MIDI, create a "boot.py" with following in it, then power cycle board: +# import usb_hid +# import usb_midi +# usb_hid.disable() +# usb_midi.enable() +# print("enabled USB MIDI, disabled USB HID") + + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=22050, buffer_size=2048) + +midi_channel = 1 +uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=31250, timeout=0.001) +midi_usb = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0) +midi_uart = adafruit_midi.MIDI(midi_in=uart, in_channel=midi_channel-1) + +amp_env_med = synthio.Envelope( + attack_time=0.05, + sustain_level=0.8, + release_time=0.2 +) + +synth = synthio.Synthesizer(channel_count=1, sample_rate=22050, envelope=amp_env_med) +# set up filters +filter_freq = 4000 +filter_res = 0.5 +filter_freq_lo = 100 # filter lowest freq +filter_freq_hi = 4500 # filter highest freq +filter_res_lo = 0.5 # filter q lowest value +filter_res_hi = 2.0 # filter q highest value +lpf = synth.low_pass_filter(filter_freq, filter_res) +note1 = synthio.Note(frequency=330, filter=lpf) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.2 + +def map_range(s, a1, a2, b1, b2): + return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) + + +while True: + note1.filter = synth.low_pass_filter(filter_freq, filter_res) + + msg = midi_uart.receive() or midi_usb.receive() + + if isinstance(msg, NoteOn) and msg.velocity != 0: + print("noteOn: ", msg.note, "vel:", msg.velocity) + note1.frequency = synthio.midi_to_hz(msg.note) + synth.press(note1) + + elif isinstance(msg, NoteOff) or isinstance(msg, NoteOn) and msg.velocity == 0: + print("noteOff:", msg.note, "vel:", msg.velocity) + note1.frequency = synthio.midi_to_hz(msg.note) + synth.release(note1) + + elif isinstance(msg, ControlChange): + print("CC", msg.control, "=", msg.value) + if msg.control == 21: # filter cutoff + filter_freq = map_range(msg.value, 0, 127, filter_freq_lo, filter_freq_hi) + elif msg.control == 22: # filter Q + filter_res = map_range(msg.value, 0, 127, filter_res_lo, filter_res_hi) + elif msg.control == 7: # volume + mixer.voice[0].level = map_range(msg.value, 0, 127, 0.0, 1.0) diff --git a/Synthio_Fundamentals/example_08_LFO_pitch.py b/Synthio_Fundamentals/example_08_LFO_pitch.py new file mode 100644 index 000000000..54629b038 --- /dev/null +++ b/Synthio_Fundamentals/example_08_LFO_pitch.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import board +import digitalio +import audiomixer +import synthio + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=44100, buffer_size=4096) +synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.1 + +lfo = synthio.LFO(rate=0.6, scale=0.05) # 1 Hz lfo at 0.25% +midi_note = 52 +note = synthio.Note(synthio.midi_to_hz(midi_note), bend=lfo) +synth.press(note) + + +while True: + pass diff --git a/Synthio_Fundamentals/example_09_LFO_tremolo.py b/Synthio_Fundamentals/example_09_LFO_tremolo.py new file mode 100644 index 000000000..a3f43be10 --- /dev/null +++ b/Synthio_Fundamentals/example_09_LFO_tremolo.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import audiomixer +import synthio + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=44100, buffer_size=4096) +synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.2 + +lfo_tremolo = synthio.LFO(rate=0.8, scale=0.3, offset=0.7) + +midi_note = 50 +note1 = synthio.Note(synthio.midi_to_hz(midi_note), amplitude=lfo_tremolo) + +synth.press(note1) + + +while True: + print(lfo_tremolo.value) + time.sleep(0.05) diff --git a/Synthio_Fundamentals/example_10_waveshapes.py b/Synthio_Fundamentals/example_10_waveshapes.py new file mode 100644 index 000000000..06cd06ed7 --- /dev/null +++ b/Synthio_Fundamentals/example_10_waveshapes.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import digitalio +import audiomixer +import synthio +import ulab.numpy as np + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=44100, buffer_size=4096) + +amp_env_slow = synthio.Envelope( + attack_time=0.15, + sustain_level=1.0, + release_time=0.8 +) +synth = synthio.Synthesizer(channel_count=1, sample_rate=44100, envelope=amp_env_slow) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.6 + +# create sine, tri, saw & square single-cycle waveforms to act as oscillators +SAMPLE_SIZE = 512 +SAMPLE_VOLUME = 32000 # 0-32767 +half_period = SAMPLE_SIZE // 2 +wave_sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * SAMPLE_VOLUME, + dtype=np.int16) +wave_saw = np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=SAMPLE_SIZE, dtype=np.int16) +wave_tri = np.concatenate((np.linspace(-SAMPLE_VOLUME, SAMPLE_VOLUME, num=half_period, + dtype=np.int16), + np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=half_period, + dtype=np.int16))) +wave_square = np.concatenate((np.full(half_period, SAMPLE_VOLUME, dtype=np.int16), + np.full(half_period, -SAMPLE_VOLUME, dtype=np.int16))) + +midi_note = 65 + + +while True: + # create notes using those waveforms + note1 = synthio.Note(synthio.midi_to_hz(midi_note), waveform=wave_sine, amplitude=1) + synth.press(note1) + time.sleep(0.75) + synth.release(note1) + time.sleep(.75) + + note1 = synthio.Note(synthio.midi_to_hz(midi_note), waveform=wave_tri, amplitude=0.7) + synth.press(note1) + time.sleep(0.75) + synth.release(note1) + time.sleep(.75) + + note1 = synthio.Note(synthio.midi_to_hz(midi_note), waveform=wave_saw, amplitude=0.25) + synth.press(note1) + time.sleep(0.75) + synth.release(note1) + time.sleep(.75) + + note1 = synthio.Note(synthio.midi_to_hz(midi_note), waveform=wave_square, amplitude=0.2) + synth.press(note1) + time.sleep(0.75) + synth.release(note1) + time.sleep(.75) diff --git a/Synthio_Fundamentals/example_11_wavetable_morph.py b/Synthio_Fundamentals/example_11_wavetable_morph.py new file mode 100644 index 000000000..5965eb920 --- /dev/null +++ b/Synthio_Fundamentals/example_11_wavetable_morph.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import audiomixer +import digitalio +import synthio +import ulab.numpy as np + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=44100, buffer_size=4096) + +amp_env_slow = synthio.Envelope( + attack_time=0.25, + sustain_level=1.0, + release_time=0.8 +) +synth = synthio.Synthesizer(channel_count=1, sample_rate=44100, envelope=amp_env_slow) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.6 + +# create sine, tri, saw & square single-cycle waveforms to act as oscillators +SAMPLE_SIZE = 512 +SAMPLE_VOLUME = 32000 # 0-32767 +half_period = SAMPLE_SIZE // 2 +wave_sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * SAMPLE_VOLUME, + dtype=np.int16) +wave_saw = np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=SAMPLE_SIZE, dtype=np.int16) +wave_tri = np.concatenate((np.linspace(-SAMPLE_VOLUME, SAMPLE_VOLUME, num=half_period, + dtype=np.int16), + np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=half_period, + dtype=np.int16))) +wave_square = np.concatenate((np.full(half_period, SAMPLE_VOLUME, dtype=np.int16), + np.full(half_period, -SAMPLE_VOLUME, dtype=np.int16))) + +def lerp(a, b, t): # function to morph shapes w linear interpolation + return (1-t) * a + t * b + +wave_empty = np.zeros(SAMPLE_SIZE, dtype=np.int16) # empty buffer we use array slice copy "[:]" on +note1 = synthio.Note(frequency=440, waveform=wave_empty, amplitude=0.6) +synth.press(note1) + +pos = 0 +my_wave = wave_empty + + +while True: + while pos <= 1.0: + print(pos) + pos += 0.01 + my_wave[:] = lerp(wave_sine, wave_saw, pos) + note1.waveform = my_wave + time.sleep(0.05) + while pos >= 0.1: + print(pos) + pos -= 0.01 + my_wave[:] = lerp(wave_sine, wave_saw, pos) + note1.waveform = my_wave + time.sleep(0.05) diff --git a/Synthio_Fundamentals/example_12_detuned_oscs.py b/Synthio_Fundamentals/example_12_detuned_oscs.py new file mode 100644 index 000000000..c6633f34a --- /dev/null +++ b/Synthio_Fundamentals/example_12_detuned_oscs.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2023 John Park and @todbot / Tod Kurt +# +# SPDX-License-Identifier: MIT + +import time +import board +import audiomixer +import digitalio +import synthio +import ulab.numpy as np + +# for PWM audio with an RC filter +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(channel_count=1, sample_rate=44100, buffer_size=4096) + +amp_env_slow = synthio.Envelope( + attack_time=0.65, + sustain_level=1.0, + release_time=0.8 +) +synth = synthio.Synthesizer(channel_count=1, sample_rate=44100, envelope=amp_env_slow) + +audio.play(mixer) +mixer.voice[0].play(synth) +mixer.voice[0].level = 0.3 + +# create sine, tri, saw & square single-cycle waveforms to act as oscillators +SAMPLE_SIZE = 512 +SAMPLE_VOLUME = 32000 # 0-32767 +half_period = SAMPLE_SIZE // 2 +wave_sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * SAMPLE_VOLUME, + dtype=np.int16) +wave_saw = np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=SAMPLE_SIZE, dtype=np.int16) +wave_tri = np.concatenate((np.linspace(-SAMPLE_VOLUME, SAMPLE_VOLUME, num=half_period, + dtype=np.int16), + np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=half_period, + dtype=np.int16))) +wave_square = np.concatenate((np.full(half_period, SAMPLE_VOLUME, dtype=np.int16), + np.full(half_period, -SAMPLE_VOLUME, dtype=np.int16))) + +# note1 = synthio.Note( frequency = 220, waveform = wave_sine, amplitude=0.3) + +detune = 0.003 # how much to detune +num_oscs = 1 +midi_note = 52 + +while True: + print("num_oscs:", num_oscs) + notes = [] # holds note objs being pressed + # simple detune, always detunes up + for i in range(num_oscs): + f = synthio.midi_to_hz(midi_note) * (1 + i*detune) + notes.append(synthio.Note(frequency=f, waveform=wave_saw)) + synth.press(notes) + time.sleep(3.6) + synth.release(notes) + time.sleep(0.1) + # increment number of detuned oscillators + num_oscs = num_oscs+1 if num_oscs < 5 else 1 diff --git a/Synthio_Fundamentals/example_13_wavetable_scanning.py b/Synthio_Fundamentals/example_13_wavetable_scanning.py new file mode 100644 index 000000000..bab57c77b --- /dev/null +++ b/Synthio_Fundamentals/example_13_wavetable_scanning.py @@ -0,0 +1,174 @@ +# SPDX-FileCopyrightText: 2023 @todbot / Tod Kurt w mods by John Park +# +# SPDX-License-Identifier: MIT + +# wavetable_midisynth_code_i2s.py -- simple wavetable synth that responds to MIDI +# 26 Jul 2023 - @todbot / Tod Kurt +# Demonstrate using wavetables to make a MIDI synth +# Needs WAV files from waveeditonline.com +# - BRAIDS01.WAV - http://waveeditonline.com/index-17.html + +import time +import busio +import board +import audiomixer +import synthio +import digitalio +import audiobusio +import ulab.numpy as np +import adafruit_wave + +import usb_midi +import adafruit_midi +from adafruit_midi.note_on import NoteOn +from adafruit_midi.note_off import NoteOff +from adafruit_midi.control_change import ControlChange + +auto_play = False # set to true to have it play its own little song +auto_play_notes = [36, 38, 40, 41, 43, 45, 46, 48, 50, 52] +auto_play_speed = 0.9 # time in seconds between notes + +midi_channel = 1 + +wavetable_fname = "wav/BRAIDS01.WAV" # from http://waveeditonline.com/index-17.html +wavetable_sample_size = 256 # number of samples per wave in wavetable (256 is standard) +sample_rate = 44100 +wave_lfo_min = 0 # which wavetable number to start from 10 +wave_lfo_max = 6 # which wavetable number to go up to 25 + +# for PWM audio with an RC filter +# Pins used on QTPY RP2040: +# - board.MOSI - Audio PWM output (needs RC filter output) +# import audiopwmio +# audio = audiopwmio.PWMAudioOut(board.GP10) + +# for I2S audio with external I2S DAC board +# import audiobusio + +# I2S on Audio BFF or Amp BFF on QT Py: +# audio = audiobusio.I2SOut(bit_clock=board.A3, word_select=board.A2, data=board.A1) + +# I2S audio on PropMaker Feather RP2040 +power = digitalio.DigitalInOut(board.EXTERNAL_POWER) +power.switch_to_output(value=True) +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) + +mixer = audiomixer.Mixer(buffer_size=4096, voice_count=1, sample_rate=sample_rate, channel_count=1, + bits_per_sample=16, samples_signed=True) +audio.play(mixer) # attach mixer to audio playback +synth = synthio.Synthesizer(sample_rate=sample_rate) +mixer.voice[0].play(synth) # attach synth to mixer +mixer.voice[0].level = 1 + +uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=31250, timeout=0.001) +midi_uart = adafruit_midi.MIDI(midi_in=uart, in_channel=midi_channel-1) +midi_usb = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=midi_channel-1) + +# mix between values a and b, works with numpy arrays too, t ranges 0-1 +def lerp(a, b, t): + return (1-t)*a + t*b + +class Wavetable: + """ A 'waveform' for synthio.Note that uses a wavetable w/ a scannable wave position.""" + def __init__(self, filepath, wave_len=256): + self.w = adafruit_wave.open(filepath) + self.wave_len = wave_len # how many samples in each wave + if self.w.getsampwidth() != 2 or self.w.getnchannels() != 1: + raise ValueError("unsupported WAV format") + self.waveform = np.zeros(wave_len, dtype=np.int16) # empty buffer we'll copy into + self.num_waves = self.w.getnframes() // self.wave_len + self.set_wave_pos(0) + + def set_wave_pos(self, pos): + """Pick where in wavetable to be, morphing between waves""" + pos = min(max(pos, 0), self.num_waves-1) # constrain + samp_pos = int(pos) * self.wave_len # get sample position + self.w.setpos(samp_pos) + waveA = np.frombuffer(self.w.readframes(self.wave_len), dtype=np.int16) + self.w.setpos(samp_pos + self.wave_len) # one wave up + waveB = np.frombuffer(self.w.readframes(self.wave_len), dtype=np.int16) + pos_frac = pos - int(pos) # fractional position between wave A & B + self.waveform[:] = lerp(waveA, waveB, pos_frac) # mix waveforms A & B + + +wavetable1 = Wavetable(wavetable_fname, wave_len=wavetable_sample_size) + +amp_env = synthio.Envelope(attack_level=0.2, sustain_level=0.2, attack_time=0.05, release_time=0.3, + decay_time=.5) +wave_lfo = synthio.LFO(rate=0.2, waveform=np.array((0, 32767), dtype=np.int16)) +lpf = synth.low_pass_filter(4000, 1) # cut some of the annoying harmonics + +synth.blocks.append(wave_lfo) # attach wavelfo to global lfo runner since cannot attach to note + +notes_pressed = {} # keys = midi note num, value = synthio.Note, + +def note_on(notenum): + # release old note at this notenum if present + if oldnote := notes_pressed.pop(notenum, None): + synth.release(oldnote) + + if not auto_play: + wave_lfo.retrigger() + + f = synthio.midi_to_hz(notenum) + + vibrato_lfo = synthio.LFO(rate=1, scale=0.01) + note = synthio.Note(frequency=f, waveform=wavetable1.waveform, + envelope=amp_env, filter=lpf, bend=vibrato_lfo) + synth.press(note) + notes_pressed[notenum] = note + +def note_off(notenum): + if note := notes_pressed.pop(notenum, None): + synth.release(note) + +def set_wave_lfo_minmax(wmin, wmax): + scale = (wmax - wmin) + wave_lfo.scale = scale + wave_lfo.offset = wmin + +last_synth_update_time = 0 +def update_synth(): + # pylint: disable=global-statement + global last_synth_update_time + # only update 100 times a sec to lighten the load + if time.monotonic() - last_synth_update_time > 0.01: + # last_update_time = time.monotonic() + wavetable1.set_wave_pos( wave_lfo.value ) + +last_auto_play_time = 0 +auto_play_pos = -1 +def update_auto_play(): + # pylint: disable=global-statement + global last_auto_play_time, auto_play_pos + if auto_play and time.monotonic() - last_auto_play_time > auto_play_speed: + last_auto_play_time = time.monotonic() + note_off( auto_play_notes[ auto_play_pos ] ) + auto_play_pos = (auto_play_pos + 3) % len(auto_play_notes) + note_on( auto_play_notes[ auto_play_pos ] ) + + +set_wave_lfo_minmax(wave_lfo_min, wave_lfo_max) + +def map_range(s, a1, a2, b1, b2): + return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) + + +print("wavetable midisynth i2s. auto_play:",auto_play) + +while True: + update_synth() + update_auto_play() + + msg = midi_uart.receive() or midi_usb.receive() + + if isinstance(msg, NoteOn) and msg.velocity != 0: + note_on(msg.note) + + elif isinstance(msg,NoteOff) or isinstance(msg,NoteOn) and msg.velocity==0: + note_off(msg.note) + + elif isinstance(msg,ControlChange): + if msg.control == 21: # mod wheel + scan_low = map_range(msg.value, 0,127, 0, 64) + set_wave_lfo_minmax(scan_low, scan_low) diff --git a/Synthio_Fundamentals/wav/BRAIDS01.WAV b/Synthio_Fundamentals/wav/BRAIDS01.WAV new file mode 100755 index 000000000..692211ca9 Binary files /dev/null and b/Synthio_Fundamentals/wav/BRAIDS01.WAV differ diff --git a/Synthio_Fundamentals/wav/PLAITS01.WAV b/Synthio_Fundamentals/wav/PLAITS01.WAV new file mode 100755 index 000000000..20a931b91 Binary files /dev/null and b/Synthio_Fundamentals/wav/PLAITS01.WAV differ diff --git a/Synthio_Fundamentals/wav/PLAITS02.WAV b/Synthio_Fundamentals/wav/PLAITS02.WAV new file mode 100755 index 000000000..361d9a72a Binary files /dev/null and b/Synthio_Fundamentals/wav/PLAITS02.WAV differ diff --git a/Synthio_Fundamentals/wav/PLAITS03.WAV b/Synthio_Fundamentals/wav/PLAITS03.WAV new file mode 100755 index 000000000..9c1b5b9ed Binary files /dev/null and b/Synthio_Fundamentals/wav/PLAITS03.WAV differ diff --git a/Synthio_Fundamentals/wav/readme.txt b/Synthio_Fundamentals/wav/readme.txt new file mode 100755 index 000000000..2c1a48d27 --- /dev/null +++ b/Synthio_Fundamentals/wav/readme.txt @@ -0,0 +1,3 @@ +WaveEdit is the free, open-source wavetable editor developed by Synthesis Technology for the E370 Quad Morphing VCO and E352 Cloud Terrarium VCO Eurorack format wavetable oscillators. WaveEdit wavetables are also compatible with the Piston Honda Mark III from Industrial Music Electronics and the Qu-Bit Chord v2. You can download WaveEdit at http://synthtech.com/waveedit for Mac, Windows and Linux. + +This site presents a growing library of free wavetable banks shared by WaveEdit users via the WaveEdit Online tab within the software. All wavetables here are under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.