-
-
Save greydoubt/f94a8bb5a3f7bf87ba23a9a4b03bfea6 to your computer and use it in GitHub Desktop.
Revisions
-
todbot revised this gist
May 12, 2023 . 1 changed file with 13 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,13 +1,13 @@ # synthio_midi_synth.py - pretty usable MIDI-controlled synth using synthio in CircuitPython # 11 May 2023 - @todbot / Tod Kurt # Uses cheapie PCM5102 DAC on QTPY RP2040 # Features: # - midi velocity controls attack rate (gentle press = slow, hard press = fast) # - notes have small random detune on all oscillators to reduce phase stacking # - adjustable number of detuned oscillators per note 1-5 (midi controller 83) # - five selectable waveforms: saw, squ, sin, noisy sin, noise (midi controller 82) # - vibrato depth on mod wheel (midi controller 1) # import time,random import board, analogio import audiobusio, audiomixer @@ -41,7 +41,7 @@ audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin) mixer = audiomixer.Mixer(voice_count=1, sample_rate=SAMPLE_RATE, channel_count=1, bits_per_sample=16, samples_signed=True, buffer_size=2048 ) # buffer_size=4096 ) audio.play(mixer) # attach mixer to DAC mixer.voice[0].play(synth) # start synth engine playing wave_i = 0 # which waveform to play @@ -50,6 +50,7 @@ osc_detune = 0.01 # how much detune (fatness) notes_pressed = {} # which notes are currently being pressed, and their note objects (so we can unpress them) mod_val = 0 # ranges 0-1 def note_on(notenum, vel): at_time = max(0, 2 * (127-(vel*1.2)) / 127) # velocity controls attack time @@ -63,7 +64,8 @@ def note_on(notenum, vel): fr = f * (1 + (osc_detune*i) + (random.random()/1000) ) print("fr:",fr) notes.append( synthio.Note( frequency=fr, envelope=amp_env, waveform=waveform, bend_mode=synthio.BendMode.VIBRATO, bend_depth = 0.5 * mod_val, bend_rate = 20 * mod_val ) ) notes_pressed[notenum] = notes synth.press(notes) @@ -91,13 +93,15 @@ def note_off(notenum,vel): elif isinstance(msg,ControlChange): print("controlChange", msg.control, "=", msg.value) if msg.control == 1: # mod wheel mod_val = msg.value / 127 for notes in notes_pressed.values(): for n in notes: # adjust vibrato depth & rate for all notes n.bend_depth = 0.5 * mod_val n.bend_rate = 20 * mod_val elif msg.control == 82: # leftmost slider on minilab3 num_oscs = int( 1 + (msg.value/127) * max_oscs ) print("num_oscs:",num_oscs) elif msg.control == 83: # leftmost+1 slider on minilab3 wave_i = int( (msg.value/127) * (len(waveforms)-1) ) print("wave_i:",wave_i) -
todbot revised this gist
May 12, 2023 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,8 +2,10 @@ # 11 May 2023 - @todbot / Tod Kurt # Uses cheapie PCM5102 DAC on QTPY RP2040 # Features: # - midi velocity controls attack rate (gentle press = slow, hard press = fast) # - notes have small random detune on all oscillators to reduce phase stacking # - adjustable number of detuned oscillators per note 1-5 (midi controller 83) # - five selectable waveforms: saw, squ, sin, noisy sin, noise (midi controller 82) # - vibrato depth on mod wheel (midi controller 1) # import time,random -
todbot revised this gist
May 12, 2023 . 1 changed file with 6 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,11 @@ # synthio_midi_synth.py - pretty usable MIDI-controlled synth using synthio in CircuitPython # 11 May 2023 - @todbot / Tod Kurt # Uses cheapie PCM5102 DAC on QTPY RP2040 # Features: # - adjustable number of oscillators per note 1-5 (midi controller 83) # - three selectable waveforms: saw, squ, sin (midi controller 82) # - vibrato depth on mod wheel (midi controller 1) # import time,random import board, analogio import audiobusio, audiomixer -
todbot revised this gist
May 12, 2023 . 1 changed file with 6 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,11 +1,6 @@ # synthio_midi_synth.py # 11 May 2023 - @todbot / Tod Kurt # Uses cheapie PCM5102 DAC on QTPY RP2040 import time,random import board, analogio import audiobusio, audiomixer @@ -31,7 +26,9 @@ wave_saw = np.linspace(VOLUME, -VOLUME, num=SAMPLE_SIZE, dtype=np.int16) wave_squ = np.concatenate((np.ones(SAMPLE_SIZE//2, dtype=np.int16)*VOLUME,np.ones(SAMPLE_SIZE//2, dtype=np.int16)*-VOLUME)) wave_sin = np.array(np.sin(np.linspace(0, 4*np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, dtype=np.int16) wave_noise = np.array([random.randint(-VOLUME, VOLUME) for i in range(SAMPLE_SIZE)], dtype=np.int16) wave_sin_dirty = np.array( wave_sin + (wave_noise/4), dtype=np.int16) waveforms = (wave_saw, wave_squ, wave_sin, wave_sin_dirty, wave_noise) synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE) # note no envelope or waveform, we do that in Note now! audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin) @@ -48,7 +45,8 @@ notes_pressed = {} # which notes are currently being pressed, and their note objects (so we can unpress them) def note_on(notenum, vel): at_time = max(0, 2 * (127-(vel*1.2)) / 127) # velocity controls attack time amp_env = synthio.Envelope(attack_time=at_time, decay_time=0.05, release_time=0.8, attack_level=1, sustain_level=0.8) waveform = waveforms[wave_i] notes = [] @@ -96,4 +94,3 @@ def note_off(notenum,vel): elif msg.control == 83: # leftmost+1 slider on minilab3 wave_i = int( (msg.value/127) * (len(waveforms)-1) ) print("wave_i:",wave_i) -
todbot created this gist
May 12, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,99 @@ # synthio_midi_synth.py -- pretty usable MIDI-controlled synth using synthio # 11 May 2023 - @todbot / Tod Kurt # Uses cheapie PCM5102 DAC on QTPY RP2040 # Features: # - adjustable number of oscillators per note 1-5 (midi controller 83) # - three selectable waveforms: saw, squ, sin (midi controller 82) # - vibrato depth on mod wheel (midi controller 1) # import time,random import board, analogio import audiobusio, audiomixer import synthio import ulab.numpy as np 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 import neopixel led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0 ) # qtpy rp2040 SPI pins, be sure PCM5102 SCK is tied to Gnd lck_pin, bck_pin, dat_pin = board.MISO, board.MOSI, board.SCK SAMPLE_RATE = 28000 # clicks @ 36kHz & 48kHz on rp2040 SAMPLE_SIZE = 256 # we like powers of two VOLUME = 12000 # 16384 is max volume I think wave_saw = np.linspace(VOLUME, -VOLUME, num=SAMPLE_SIZE, dtype=np.int16) wave_squ = np.concatenate((np.ones(SAMPLE_SIZE//2, dtype=np.int16)*VOLUME,np.ones(SAMPLE_SIZE//2, dtype=np.int16)*-VOLUME)) wave_sin = np.array(np.sin(np.linspace(0, 4*np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, dtype=np.int16) waveforms = (wave_saw, wave_squ, wave_sin) synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE) # note no envelope or waveform, we do that in Note now! audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin) mixer = audiomixer.Mixer(voice_count=1, sample_rate=SAMPLE_RATE, channel_count=1, bits_per_sample=16, samples_signed=True, buffer_size=2048 ) # buffer_size=4096 ) audio.play(mixer) # attach mixer to DAC mixer.voice[0].play(synth) # start synth engine playing wave_i = 0 # which waveform to play num_oscs = 1 # how many oscillators per note max_oscs = 5 osc_detune = 0.01 # how much detune (fatness) notes_pressed = {} # which notes are currently being pressed, and their note objects (so we can unpress them) def note_on(notenum, vel): amp_env = synthio.Envelope(attack_time=0.1, decay_time=0.05, release_time=0.8, attack_level=1, sustain_level=0.8) waveform = waveforms[wave_i] notes = [] f = synthio.midi_to_hz(notenum) for i in range(num_oscs): # add detuning to oscillators + a bit of random so phases w/ other notes don't perfectly align fr = f * (1 + (osc_detune*i) + (random.random()/1000) ) print("fr:",fr) notes.append( synthio.Note( frequency=fr, envelope=amp_env, waveform=waveform, vibrato_depth=0.0, vibrato_rate=3 ) ) notes_pressed[notenum] = notes synth.press(notes) def note_off(notenum,vel): notes = notes_pressed.get(notenum, None) if notes: synth.release(notes) del notes_pressed[notenum] debug_notes = False print("synthio_midi_synth ready") while True: msg = midi.receive() if isinstance(msg, NoteOn) and msg.velocity != 0: print("noteOn: ", msg.note, "vel=", msg.velocity) led.fill(0xff00ff) note_on( msg.note, msg.velocity) if debug_notes: print("notes_pressed:", notes_pressed) elif isinstance(msg,NoteOff) or isinstance(msg,NoteOn) and msg.velocity==0: print("noteOff:", msg.note, "vel=", msg.velocity) led.fill(0x00000) note_off( msg.note, msg.velocity) if debug_notes: print("notes_pressed:", notes_pressed) elif isinstance(msg,ControlChange): print("controlChange", msg.control, "=", msg.value) if msg.control == 1: # mod wheel for notes in notes_pressed.values(): for n in notes: #n.vibrato_rate = 20 * (msg.value/127) # this perhaps does not work? n.vibrato_depth = (msg.value/127) elif msg.control == 82: # leftmost slider on minilab3 num_oscs = int( 1 + (msg.value/127) * max_oscs ) print("num_oscs:",num_oscs) elif msg.control == 83: # leftmost+1 slider on minilab3 wave_i = int( (msg.value/127) * (len(waveforms)-1) ) print("wave_i:",wave_i)