Examples

Basic

The most basic example of initiating an audio output and synth object.

examples/basic.py
 1# pico_synth_sandbox - Basic Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import time
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.audio import get_audio_driver
 8from pico_synth_sandbox.synth import Synth
 9from pico_synth_sandbox.voice.oscillator import Oscillator
10
11board = get_board()
12audio = get_audio_driver(board)
13synth = Synth(audio)
14synth.add_voice(Oscillator())
15
16while True:
17    synth.press(notenum=46)
18    time.sleep(1)
19    synth.release()
20    time.sleep(1)

Simple Synth

Use the display, touch keyboard, and encoder to control a basic synth object.

examples/simple.py
 1# pico_synth_sandbox - Simple Synth Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9from pico_synth_sandbox.keyboard import get_keyboard_driver
10from pico_synth_sandbox.arpeggiator import Arpeggiator
11from pico_synth_sandbox.audio import get_audio_driver
12from pico_synth_sandbox.synth import Synth
13from pico_synth_sandbox.voice.oscillator import Oscillator
14
15board = get_board()
16
17display = Display(board)
18display.write("PicoSynthSandbox", (0,0))
19display.write("Loading...", (0,1))
20display.force_update()
21
22audio = get_audio_driver(board)
23synth = Synth(audio)
24synth.add_voices(Oscillator() for i in range(4))
25
26keyboard = get_keyboard_driver(board, max_voices = len(synth.voices))
27arpeggiator = Arpeggiator()
28keyboard.set_arpeggiator(arpeggiator)
29
30def voice_press(voice, notenum, velocity, keynum=None):
31    synth.press(voice, notenum, velocity)
32keyboard.set_voice_press(voice_press)
33
34def voice_release(voice, notenum, keynum=None):
35    synth.release(voice)
36keyboard.set_voice_release(voice_release)
37
38def key_press(keynum, notenum, velocity):
39    display.write("*", (keynum,1), 1)
40keyboard.set_key_press(key_press)
41
42def key_release(keynum, notenum):
43    display.write("_", (keynum,1), 1)
44keyboard.set_key_release(key_release)
45
46encoder = Encoder(board)
47def click():
48    arpeggiator.toggle()
49encoder.set_click(click)
50encoder.set_long_press(click)
51
52display.write("_"*len(keyboard.keys), (0,1))
53
54pico_synth_sandbox.tasks.run()

Filtered Synth

Use the display, touch keyboard, and encoder to control a more advanced synth object with amplitude envelope and filter settings. Filter uses both an envelope and low-frequency oscillator (“lfo”). Synth object must call update function in program loop for envelope and lfo to operate.

examples/filter.py
 1# pico_synth_sandbox - Filtered Synth Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9from pico_synth_sandbox.audio import get_audio_driver
10from pico_synth_sandbox.synth import Synth
11from pico_synth_sandbox.voice.oscillator import Oscillator
12import pico_synth_sandbox.waveform as waveform
13from pico_synth_sandbox.keyboard import get_keyboard_driver
14from pico_synth_sandbox.arpeggiator import Arpeggiator
15
16board = get_board()
17
18display = Display(board)
19display.write("PicoSynthSandbox", (0,0))
20display.write("Loading...", (0,1))
21display.force_update()
22
23audio = get_audio_driver(board)
24synth = Synth(audio)
25synth.add_voices(Oscillator() for i in range(4))
26synth.set_waveform(waveform.get_saw())
27for voice in synth.voices:
28    voice.set_envelope(
29        attack_time=0.1,
30        decay_time=0.2,
31        release_time=1.0,
32        attack_level=1.0,
33        sustain_level=0.5
34    )
35    voice.set_filter(
36        type=Synth.FILTER_LPF,
37        frequency=1.0,
38        resonance=0.5,
39        envelope_attack_time=1.0,
40        envelope_release_time=1.0,
41        envelope_amount=0.05,
42        lfo_rate=0.5,
43        lfo_depth=0.05,
44        synth=synth
45    )
46
47keyboard = get_keyboard_driver(board, max_voices=len(synth.voices))
48arpeggiator = Arpeggiator()
49arpeggiator.set_octaves(1)
50arpeggiator.set_bpm(80)
51arpeggiator.set_steps(Arpeggiator.STEP_EIGHTH)
52arpeggiator.set_gate(0.5)
53keyboard.set_arpeggiator(arpeggiator)
54
55def press(voice, notenum, velocity, keynum=None):
56    synth.press(voice, notenum, velocity)
57keyboard.set_press(press)
58
59def release(voice, notenum, keynum=None):
60    synth.release(voice)
61keyboard.set_release(release)
62
63def key_press(keynum, notenum, velocity):
64    display.write("*", (keynum,1), 1)
65keyboard.set_key_press(key_press)
66
67def key_release(keynum, notenum):
68    display.write("_", (keynum,1), 1)
69keyboard.set_key_release(key_release)
70
71mod_value = 127
72encoder = Encoder(board)
73def update_filter():
74    synth.set_filter_frequency(float(mod_value) / 127.0)
75def increment():
76    global mod_value
77    if mod_value < 127:
78        mod_value += 1
79        update_filter()
80def decrement():
81    global mod_value
82    if mod_value > 0:
83        mod_value -= 1
84        update_filter()
85def click():
86    arpeggiator.toggle()
87encoder.set_increment(increment)
88encoder.set_decrement(decrement)
89encoder.set_click(click)
90encoder.set_long_press(click)
91
92display.write("_"*len(keyboard.keys), (0,1))
93
94pico_synth_sandbox.tasks.run()

Monophonic Synth

Control two different oscillators in sync with a single note from the touch keyboard. The encoder is used to increase or decrease the pitch of both oscillators by semitones.

examples/monophonic.py
  1# pico_synth_sandbox - Monophonic Synth Example
  2# 2023 Cooper Dalrymple - me@dcdalrymple.com
  3# GPL v3 License
  4
  5import pico_synth_sandbox.tasks
  6from pico_synth_sandbox.board import get_board
  7from pico_synth_sandbox.display import Display
  8from pico_synth_sandbox.encoder import Encoder
  9from pico_synth_sandbox.keyboard import get_keyboard_driver
 10from pico_synth_sandbox.audio import get_audio_driver
 11from pico_synth_sandbox.synth import Synth
 12from pico_synth_sandbox.voice.oscillator import Oscillator
 13import pico_synth_sandbox.waveform as waveform
 14
 15board = get_board()
 16
 17display = Display(board)
 18display.write("PicoSynthSandbox", (0,0))
 19display.write("Loading...", (0,1))
 20display.force_update()
 21
 22note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
 23
 24audio = get_audio_driver(board)
 25synth = Synth(audio)
 26
 27osc1 = Oscillator()
 28osc1.set_glide(1.0)
 29osc1.set_waveform(waveform.get_sine())
 30osc1.set_pan_rate(0.5)
 31osc1.set_pan_depth(0.1)
 32osc1.set_vibrato_rate(4.0)
 33osc1.set_vibrato_depth(0.1)
 34osc1.set_envelope(
 35    attack_time=0.1,
 36    attack_level=1.0,
 37    decay_time=0.5,
 38    sustain_level=0.25,
 39    release_time=1.0
 40)
 41osc1.set_filter(
 42    frequency=0.1,
 43    resonance=0.5,
 44    envelope_attack_time=0.5,
 45    envelope_release_time=1.0,
 46    envelope_amount=0.25,
 47    lfo_rate=0.1,
 48    lfo_depth=0.25,
 49    synth=synth
 50)
 51osc1.set_coarse_tune(-1.0)
 52synth.add_voice(osc1)
 53
 54osc2 = Oscillator()
 55osc2.set_glide(0.25)
 56osc2.set_waveform(waveform.get_sine())
 57osc2.set_pan_rate(2.0)
 58osc2.set_pan_depth(0.5)
 59osc2.set_vibrato_rate(1.0)
 60osc2.set_vibrato_depth(0.05)
 61osc2.set_envelope(
 62    attack_time=1.0,
 63    attack_level=1.0,
 64    decay_time=2.0,
 65    sustain_level=0.0,
 66    release_time=2.0
 67)
 68osc2.set_filter(
 69    frequency=0.5,
 70    resonance=0.1,
 71    envelope_attack_time=2.0,
 72    envelope_release_time=2.0,
 73    envelope_amount=0.15,
 74    synth=synth
 75)
 76osc2.set_coarse_tune(2.0)
 77osc2.set_level(0.5)
 78synth.add_voice(osc2)
 79
 80keyboard = get_keyboard_driver(board, max_voices=1)
 81
 82def press(voicenum, notenum, velocity, keynum=None):
 83    for voice in synth.voices:
 84        synth.press(voice, notenum, velocity)
 85    display.write("{:02d} {}".format(notenum, note_names[notenum % 12]), (0,1), 6)
 86keyboard.set_voice_press(press)
 87
 88def release(voicenum, notenum, keynum=None):
 89    if not keyboard.has_notes():
 90        synth.release()
 91        display.write("", (0, 1), 6)
 92keyboard.set_voice_release(release)
 93
 94tune = 0
 95encoder = Encoder(board)
 96def update_tuning():
 97    global tune
 98    osc1.set_coarse_tune(tune / 12.0)
 99    osc2.set_coarse_tune(tune / 12.0 + 2.0)
100    display.write("{:02d}".format(tune), (6,1), 6, True)
101def increment():
102    global tune
103    if tune < 48:
104        tune += 1
105    update_tuning()
106def decrement():
107    global tune
108    if tune > -48:
109        tune -= 1
110    update_tuning()
111encoder.set_increment(increment)
112encoder.set_decrement(decrement)
113
114display.write("", (0,1), 6)
115update_tuning()
116
117pico_synth_sandbox.tasks.run()

Midi Controller

Use the touch keyboard and encoder to output midi note and control messages through the UART midi interface.

examples/midi.py
 1# pico_synth_sandbox - Midi Controller Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9from pico_synth_sandbox.midi import Midi
10from pico_synth_sandbox.keyboard import get_keyboard_driver
11from pico_synth_sandbox.arpeggiator import Arpeggiator
12
13board = get_board()
14
15display = Display(board)
16display.write("PicoSynthSandbox", (0,0))
17display.write("Loading...", (0,1))
18display.force_update()
19
20mod_value = 0
21
22midi = Midi(board)
23midi.set_channel(1)
24midi.set_thru(True)
25
26keyboard = get_keyboard_driver(board, max_voices=0)
27arpeggiator = Arpeggiator()
28keyboard.set_arpeggiator(arpeggiator)
29
30def key_press(keynum, notenum, velocity):
31    midi.send_note_on(notenum, velocity)
32    display.write("*", (keynum,1), 1)
33keyboard.set_key_press(key_press)
34
35def key_release(keynum, notenum):
36    midi.send_note_off(notenum)
37    display.write("_", (keynum,1), 1)
38keyboard.set_key_release(key_release)
39
40encoder = Encoder(board)
41def increment():
42    global mod_value
43    if mod_value < 127:
44        mod_value += 1
45        midi.send_control_change(1, mod_value)
46        display.write(str(mod_value), (13,0), 3, True)
47def decrement():
48    global mod_value
49    if mod_value > 0:
50        mod_value -= 1
51        midi.send_control_change(1, mod_value)
52        display.write(str(mod_value), (13,0), 3, True)
53def click():
54    arpeggiator.toggle()
55encoder.set_increment(increment)
56encoder.set_decrement(decrement)
57encoder.set_click(click)
58
59def control_change(control, value):
60    if control == 64: # Sustain
61        keyboard.set_sustain(value)
62midi.set_control_change(control_change)
63
64display.write("CH:"+str(midi.get_channel()))
65display.write(str(mod_value), (13,0), 3, True)
66display.write("_"*len(keyboard.keys), (0,1))
67
68pico_synth_sandbox.tasks.run()

Drums

Use the display, touch keyboard, and encoder to play synthesized drum samples using the provided Drum classes. Encoder controls decay time of closed and open hi hat.

examples/drums.py
 1# pico_synth_sandbox - Drums Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9from pico_synth_sandbox.audio import get_audio_driver
10from pico_synth_sandbox.synth import Synth
11from pico_synth_sandbox.voice.drum import Kick, Snare, ClosedHat, OpenHat
12from pico_synth_sandbox.keyboard import get_keyboard_driver
13from pico_synth_sandbox.arpeggiator import Arpeggiator
14
15board = get_board()
16
17display = Display(board)
18display.write("PicoSynthSandbox", (0,0))
19display.write("Loading...", (0,1))
20display.force_update()
21
22audio = get_audio_driver(board)
23synth = Synth(audio)
24synth.add_voice(Kick())
25synth.add_voice(Snare())
26# Keep local variable for changing envelope
27closed_hat = ClosedHat()
28open_hat = OpenHat()
29synth.add_voice(closed_hat)
30synth.add_voice(open_hat)
31
32keyboard = get_keyboard_driver(board, max_voices=1)
33arpeggiator = Arpeggiator()
34keyboard.set_arpeggiator(arpeggiator)
35
36def voice_press(voice, notenum, velocity, keynum=None):
37    voice = (notenum - keyboard.root) % len(synth.voices) # Ignore voice allocation
38    synth.press(voice, notenum, velocity)
39    if voice == 2: # Closed Hat
40        synth.release(open_hat, True) # Force release
41keyboard.set_voice_press(voice_press)
42
43def voice_release(voice, notenum, keynum=None):
44    voice = (notenum - keyboard.root) % len(synth.voices) # Ignore voice allocation
45    synth.release(voice)
46keyboard.set_voice_release(voice_release)
47
48def key_press(keynum, notenum, velocity):
49    display.write("*", (keynum,1), 1)
50keyboard.set_key_press(key_press)
51
52def key_release(keynum, notenum):
53    display.write("_", (keynum,1), 1)
54keyboard.set_key_release(key_release)
55
56mod_value = 64
57encoder = Encoder(board)
58def update_envelope():
59    closed_hat.set_time(float(mod_value) / 127.0)
60    open_hat.set_time(float(mod_value) / 127.0)
61def increment():
62    global mod_value
63    if mod_value < 127:
64        mod_value += 1
65        update_envelope()
66def decrement():
67    global mod_value
68    if mod_value > 0:
69        mod_value -= 1
70        update_envelope()
71def click():
72    arpeggiator.toggle()
73encoder.set_increment(increment)
74encoder.set_decrement(decrement)
75encoder.set_click(click)
76encoder.set_long_press(click)
77
78display.write("_"*len(keyboard.keys), (0,1))
79
80pico_synth_sandbox.tasks.run()

Drum Sequencer

Utilizes the Sequencer class to create a simple drum sequencer using the display, touch keyboard, encoder, and synthesized drum samples (similar to the “Drums” example). Encoder controls either the selected voice or beats per minute (aka “bpm”) and is switched by clicking the encoder. There is a total of 16 steps used by the sequencer, and the first 8 keys can be used to toggle whether a note is played on that particular step for the selected voice. The 12th key is used to switch between the lower and upper 8 steps. The 9th through 11th keys are not used in this example.

examples/sequencer.py
  1# pico_synth_sandbox - Drum Sequencer Example
  2# 2023 Cooper Dalrymple - me@dcdalrymple.com
  3# GPL v3 License
  4
  5import pico_synth_sandbox.tasks
  6from pico_synth_sandbox.board import get_board
  7from pico_synth_sandbox.display import Display
  8from pico_synth_sandbox.encoder import Encoder
  9from pico_synth_sandbox.keyboard import get_keyboard_driver
 10from pico_synth_sandbox.sequencer import Sequencer
 11from pico_synth_sandbox.audio import get_audio_driver
 12from pico_synth_sandbox.synth import Synth
 13from pico_synth_sandbox.voice.drum import Kick, Snare, ClosedHat, OpenHat
 14
 15board = get_board()
 16
 17display = Display(board)
 18display.write("PicoSynthSandbox", (0,0))
 19display.write("Loading...", (0,1))
 20display.force_update()
 21display.set_cursor_enabled(True)
 22display.set_cursor_blink(False)
 23
 24# Local parameters
 25voice=0
 26bpm=120
 27alt_enc=False
 28alt_key=False
 29
 30audio = get_audio_driver(board)
 31synth = Synth(audio)
 32synth.add_voices([
 33    Kick(),
 34    Snare(),
 35    ClosedHat(),
 36    OpenHat()
 37])
 38
 39sequencer = Sequencer(
 40    tracks=4,
 41    bpm=120
 42)
 43def seq_step(position):
 44    display.set_cursor_position(position, 1)
 45def seq_press(notenum, velocity):
 46    synth.press((notenum - 1) % len(synth.voices))
 47def seq_release(notenum):
 48    if (notenum - 1) % len(synth.voices) == 2: # Closed Hat
 49        synth.release(3, True) # Force release Open Hat
 50    synth.release((notenum - 1) % len(synth.voices))
 51sequencer.set_step(seq_step)
 52sequencer.set_press(seq_press)
 53sequencer.set_release(seq_release)
 54
 55def update_display():
 56    display.write(synth.voices[voice].__qualname__, (0, 0), 11)
 57    display.write(">" if alt_enc else "<", (11,0), 1)
 58    display.write(("^" if alt_key else "-") if len(keyboard.keys) < 16 else " ", (12,0), 1)
 59    display.write(str(bpm), (13,0), 3, True)
 60    line = ""
 61    for i in range(sequencer.get_length()):
 62        line += "*" if sequencer.has_note(i, voice) else "_"
 63    display.write(line, (0,1))
 64
 65keyboard = get_keyboard_driver(board, max_voices=0)
 66def key_press(keynum, notenum, velocity):
 67    global sequencer
 68    
 69    position = keynum
 70    if len(keyboard.keys) < 16:
 71        global alt_key
 72        if keynum == 11:
 73            alt_key = not alt_key
 74            display.write("^" if alt_key else "-", (12,0), 1)
 75            return
 76        elif keynum < 8:
 77            position = keynum + (8 if alt_key else 0)
 78
 79    position = position % sequencer.get_length()
 80    if not sequencer.has_note(
 81        position=position,
 82        track=voice
 83    ):
 84        sequencer.set_note(
 85            position=position,
 86            notenum=voice+1,
 87            velocity=1.0,
 88            track=voice
 89        )
 90        display.write("*", (position,1), 1)
 91    else:
 92        sequencer.remove_note(
 93            position=position,
 94            track=voice
 95        )
 96        display.write("_", (position,1), 1)
 97keyboard.set_key_press(key_press)
 98
 99def update_alt_enc():
100    global alt_enc
101    display.write(">" if alt_enc else "<", (11,0), 1)
102
103def update_bpm():
104    global bpm
105    sequencer.set_bpm(bpm)
106    display.write(str(bpm), (13,0), 3, True)
107
108def increment_track():
109    global alt_enc, voice
110    if alt_enc:
111        alt_enc = False
112        update_alt_enc()
113    voice = (voice + 1) % sequencer.get_tracks()
114    update_display()
115def decrement_track():
116    global alt_enc, voice
117    if alt_enc:
118        alt_enc = False
119        update_alt_enc()
120    voice = (voice - 1) % sequencer.get_tracks()
121    update_display()
122
123def increment_bpm():
124    global alt_enc, bpm
125    if not alt_enc:
126        alt_enc = True
127        update_alt_enc()
128    if bpm < 200:
129        bpm += 1
130    update_bpm()
131def decrement_bpm():
132    global alt_enc, bpm
133    if not alt_enc:
134        alt_enc = True
135        update_alt_enc()
136    if bpm > 50:
137        bpm -= 1
138    update_bpm()
139
140if board.num_encoders() == 1:
141
142    encoder = Encoder(board)
143
144    def increment():
145        global alt_enc
146        if not alt_enc:
147            increment_track()
148        else:
149            increment_bpm()
150    encoder.set_increment(increment)
151
152    def decrement():
153        global alt_enc
154        if not alt_enc:
155            decrement_track()
156        else:
157            decrement_bpm()
158    encoder.set_decrement(decrement)
159
160    def click():
161        global alt_enc
162        alt_enc = not alt_enc
163        update_alt_enc()
164    encoder.set_click(click)
165
166elif board.num_encoders() > 1:
167
168    encoder1 = Encoder(board, 0)
169    encoder1.set_increment(increment_track)
170    encoder1.set_decrement(decrement_track)
171
172    encoder2 = Encoder(board, 1)
173    encoder2.set_increment(increment_bpm)
174    encoder2.set_decrement(decrement_bpm)
175
176update_display()
177sequencer.enable()
178
179pico_synth_sandbox.tasks.run()

Microphone Example

Display the current microphone level as a horizontal bar graph. Set the record trigger and clip level using the encoder dial. Begin the record process by long pressing the encoder. The microphone will start recording when the input level is detected above the trigger level and will continue recording until the input level is detected below the clip level. The generated wave file will be saved to /samples/test.wav. Short click the encoder to reset the max level of the volume meter.

examples/microphone.py
  1# pico_synth_sandbox - Microphone Example
  2# 2023 Cooper Dalrymple - me@dcdalrymple.com
  3# GPL v3 License
  4
  5import time
  6import pico_synth_sandbox.tasks
  7from pico_synth_sandbox.tasks import Task
  8from pico_synth_sandbox.board import get_board
  9from pico_synth_sandbox.display import Display
 10from pico_synth_sandbox.microphone import Microphone
 11from pico_synth_sandbox.encoder import Encoder
 12
 13board = get_board()
 14
 15display = Display(board)
 16display.enable_horizontal_graph()
 17display.write("PicoSynthSandbox", (0,0))
 18display.write("Loading...", (0,1))
 19display.force_update()
 20
 21microphone = Microphone(board)
 22
 23trigger_level = 25
 24trigger_level_step = 0.0001
 25
 26class MicrophoneLevel(Task):
 27    def __init__(self, microphone, update=None):
 28        self._microphone = microphone
 29        self._max_level = 0.0
 30        self._prev_max_level = 0.0
 31        self._update = update
 32        Task.__init__(self, update_frequency=5)
 33    def set_update(self, callback):
 34        self._update = callback
 35    async def update(self):
 36        self._level = self._microphone.get_level()
 37        self._prev_max_level = self._max_level
 38        self._max_level = max(self._level, self._max_level)
 39        if self._update:
 40            self._update(self._level, self._max_level, self._prev_max_level)
 41    def get_max_level(self):
 42        return self._max_level
 43mic_level = MicrophoneLevel(microphone)
 44
 45def display_trigger():
 46    global trigger_level, trigger_level_step
 47    level_x = int(min(trigger_level*trigger_level_step/mic_level.get_max_level(), 1.0)*16) if mic_level.get_max_level() > 0.0 else 0
 48    display.write(" " * level_x + chr(0xff), (0,0))
 49
 50def update_level(level, max_level, prev_max_level):
 51    if max_level != prev_max_level:
 52        display_trigger(False)
 53    if max_level > 0.0:
 54        display.write_horizontal_graph(level, 0.0, max_level, (0,1), 16)
 55    else:
 56        display.write("", (0,1), 16)
 57mic_level.set_update(update_level)
 58
 59def record_trigger():
 60    display.write("Recording", (0,0), 13)
 61microphone.set_trigger(record_trigger)
 62
 63encoder = Encoder(board)
 64
 65def increment():
 66    global trigger_level
 67    if trigger_level < 100:
 68        trigger_level += 1
 69        display_trigger()
 70encoder.set_increment(increment)
 71
 72def decrement():
 73    global trigger_level
 74    if trigger_level > 1:
 75        trigger_level -= 1
 76        display_trigger()
 77encoder.set_decrement(decrement)
 78
 79def reset_max_level():
 80    global max_level
 81    max_level = 0.0
 82encoder.set_click(reset_max_level)
 83
 84def start_record():
 85    pico_synth_sandbox.tasks.pause()
 86    display.clear()
 87    display.write("Waiting")
 88    display.force_update()
 89    microphone.record(
 90        name="test",
 91        samples=4096,
 92        trigger=trigger_level*trigger_level_step,
 93        clip=trigger_level*trigger_level_step
 94    )
 95    display.write("Complete!")
 96    display.force_update()
 97    time.sleep(1)
 98    display.clear()
 99    display_trigger()
100    pico_synth_sandbox.tasks.resume()
101encoder.set_long_press(start_record)
102
103display.clear()
104display_trigger()
105
106pico_synth_sandbox.tasks.run()

Microphone FFT Example

Use the included Fast Fourier Transform utility to calculate and display the range of frequencies from the microphone input as a row of vertical bar graphs.

examples/microphone-fft.py
 1# pico_synth_sandbox - Microphone FFT Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import math
 6import ulab.numpy as numpy
 7from pico_synth_sandbox import fft
 8from pico_synth_sandbox.board import get_board
 9from pico_synth_sandbox.display import Display
10from pico_synth_sandbox.microphone import Microphone
11
12board = get_board()
13
14display = Display(board)
15display.enable_vertical_graph()
16
17microphone = Microphone(board)
18
19segments = 16
20spectro_range = 0.33
21min_val = None
22max_val = None
23
24while True:
25    fft_data = fft(microphone.get_buffer(256), log=True, dtype=numpy.uint16)
26
27    segment_len = int(math.floor(len(fft_data)*spectro_range/segments))
28    segment_data = [0 for i in range(segments)]
29    for i in range(segments):
30        segment_data[i] = numpy.max(fft_data[i*segment_len:(i+1)*segment_len])
31
32    if min_val is None:
33        min_val = numpy.min(segment_data)
34    else:
35        min_val = min(numpy.min(segment_data), min_val)
36
37    if max_val is None:
38        max_val = numpy.max(segment_data)
39    else:
40        max_val = max(numpy.max(segment_data), max_val)
41
42    for i in range(segments):
43        display.write_vertical_graph(
44            value = segment_data[i],
45            minimum = min_val,
46            maximum = max_val,
47            position = (i,0),
48            height = 2
49        )
50    display.force_update(reset_cursor=False)

Simple Sample Playback Example

Create a Sample voice and load the included /samples/hey.wav file. File is loaded during voice initialization and only one monophonic voice is available. The sample voice is set to not loop playback. Use the encoder click to toggle the arpeggiator.

examples/sample-simple.py
 1# pico_synth_sandbox - Simple Sample Playback Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9from pico_synth_sandbox.keyboard import get_keyboard_driver
10from pico_synth_sandbox.arpeggiator import Arpeggiator
11from pico_synth_sandbox.audio import get_audio_driver
12from pico_synth_sandbox.synth import Synth
13from pico_synth_sandbox.voice.sample import Sample
14
15board = get_board()
16
17display = Display(board)
18display.write("PicoSynthSandbox", (0,0))
19display.write("Loading...", (0,1))
20display.force_update()
21
22audio = get_audio_driver(board)
23audio.mute()
24synth = Synth(audio)
25synth.add_voice(Sample(loop=False, filepath="/samples/hey.wav"))
26
27keyboard = get_keyboard_driver(board, root=60, max_voices=len(synth.voices))
28arpeggiator = Arpeggiator()
29keyboard.set_arpeggiator(arpeggiator)
30
31def voice_press(voice, notenum, velocity, keynum=None):
32    synth.press(0, notenum, velocity)
33keyboard.set_voice_press(voice_press)
34
35def voice_release(voice, notenum, keynum=None):
36    if not synth.has_notes():
37        synth.release(0)
38keyboard.set_voice_release(voice_release)
39
40def key_press(keynum, notenum, velocity):
41    display.write("*", (keynum,1), 1)
42keyboard.set_key_press(key_press)
43
44def key_release(keynum, notenum):
45    display.write("_", (keynum,1), 1)
46keyboard.set_key_release(key_release)
47
48encoder = Encoder(board)
49def click():
50    arpeggiator.toggle()
51encoder.set_click(click)
52encoder.set_long_press(click)
53
54display.write("_"*len(keyboard.keys), (0,1))
55
56audio.unmute()
57
58pico_synth_sandbox.tasks.run()

Filtered Sample Example

Create 12 Sample voices using a single looping sample, the included /samples/hey.wav file. All of the available Oscillator parameters are defined to create a unique sound. Press the encoder to toggle between controlling the tune and filter frequency of the synthesizer.

examples/sample-filter.py
  1# pico_synth_sandbox - Filtered Sample Example
  2# 2023 Cooper Dalrymple - me@dcdalrymple.com
  3# GPL v3 License
  4
  5import random
  6import pico_synth_sandbox.tasks
  7from pico_synth_sandbox import fftfreq
  8from pico_synth_sandbox.board import get_board
  9from pico_synth_sandbox.display import Display
 10from pico_synth_sandbox.encoder import Encoder
 11from pico_synth_sandbox.keyboard import get_keyboard_driver
 12from pico_synth_sandbox.audio import get_audio_driver
 13from pico_synth_sandbox.synth import Synth
 14from pico_synth_sandbox.voice.sample import Sample
 15import pico_synth_sandbox.waveform as waveform
 16
 17board = get_board()
 18
 19display = Display(board)
 20display.enable_horizontal_graph()
 21display.write("PicoSynthSandbox", (0,0))
 22display.write("Loading...", (0,1))
 23display.force_update()
 24
 25audio = get_audio_driver(board)
 26audio.mute()
 27
 28sample_data, sample_rate = waveform.load_from_file("/samples/hey.wav")
 29root = fftfreq(
 30    data=sample_data,
 31    sample_rate=sample_rate
 32)
 33
 34synth = Synth(audio)
 35synth.add_voices(Sample(loop=True) for i in range(4))
 36for voice in synth.voices:
 37    voice.load(sample_data, sample_rate, root)
 38    voice.set_envelope(
 39        attack_time=0.1,
 40        decay_time=0.2,
 41        release_time=1.0,
 42        attack_level=1.0,
 43        sustain_level=0.5
 44    )
 45    voice.set_filter(
 46        type=Synth.FILTER_LPF,
 47        frequency=1.0,
 48        resonance=0.5,
 49        envelope_attack_time=1.0,
 50        envelope_release_time=1.0,
 51        envelope_amount=0.05,
 52        lfo_rate=0.5,
 53        lfo_depth=0.05,
 54        synth=synth
 55    )
 56    voice.set_vibrato_depth(0.1)
 57    voice.set_vibrato_rate(8.0)
 58    voice.set_pan_rate(random.randint(0,80)/100.0+0.1)
 59    voice.set_pan_depth(0.8)
 60
 61keyboard = get_keyboard_driver(board, max_voices=len(synth.voices))
 62def press(voice, notenum, velocity, keynum=None):
 63    synth.press(voice, notenum, velocity)
 64keyboard.set_voice_press(press)
 65def release(voice, notenum, keynum=None):
 66    synth.release(voice)
 67keyboard.set_voice_release(release)
 68
 69type = 0
 70semitone = 0
 71filter = 100
 72
 73def update_cursor():
 74    global type
 75    display.set_cursor_position(8 if type else 0, 0)
 76update_cursor()
 77
 78def update_tune():
 79    global semitone
 80    for voice in synth.voices:
 81        voice.set_coarse_tune(semitone / 12.0)
 82    display.write_horizontal_graph(semitone, -48, 48, (0,1), 8)
 83update_tune()
 84
 85def update_filter():
 86    global filter
 87    synth.set_filter_frequency(filter / 100.0)
 88    display.write_horizontal_graph(filter, 0, 100, (8,1), 8)
 89update_filter()
 90
 91def increment_semitone():
 92    global type, semitone
 93    if type != 0:
 94        type = 0
 95        update_cursor()
 96    if semitone < 24:
 97        semitone += 1
 98        update_tune()
 99def decrement_semitone():
100    global type, semitone
101    if type != 0:
102        type = 0
103        update_cursor()
104    if semitone > -24:
105        semitone -= 1
106        update_tune()
107
108def increment_filter():
109    global type, filter
110    if type != 1:
111        type = 1
112        update_cursor()
113    if filter < 100:
114        filter += 1
115        update_filter()
116def decrement_filter():
117    global type, filter
118    if type != 1:
119        type = 1
120        update_cursor()
121    if filter > 0:
122        filter -= 1
123        update_filter()
124
125if board.num_encoders() == 1:
126
127    encoder = Encoder(board)
128
129    def increment_encoder():
130        global type
131        if type == 0:
132            increment_semitone()
133        else:
134            increment_filter()
135    encoder.set_increment(increment_encoder)
136
137    def decrement_encoder():
138        global type
139        if type == 0:
140            increment_semitone()
141        else:
142            increment_filter()
143    encoder.set_decrement(decrement_encoder)
144
145    def toggle_encoder():
146        global type
147        type = 0 if type else 1
148        display.set_cursor_position(5 if type else 0, 0)
149    encoder.set_click(toggle_encoder)
150    encoder.set_long_press(toggle_encoder)
151
152elif board.num_encoders() > 1:
153
154    encoder1 = Encoder(board, 0)
155    encoder1.set_increment(increment_semitone)
156    encoder1.set_decrement(decrement_semitone)
157
158    encoder2 = Encoder(board, 1)
159    encoder2.set_increment(increment_filter)
160    encoder2.set_decrement(decrement_filter)
161
162
163display.write("Tune    Filter  ", position=(0,0))
164display.set_cursor_enabled(True)
165display.set_cursor_blink(True)
166display.force_update()
167
168audio.unmute()
169
170pico_synth_sandbox.tasks.run()

Sample Browser Example

Browse through every wav sample available in the /samples directory. 12 Sample voices are created to play through selected sample using touch keyboard. Press the encoder to toggle between controlling the tune and scrolling through the samples. Perform a long press of the encoder to load the selected sample. An * indicator in the upper right corner of the screen will indicate if the currently selected sample is loaded.

examples/sample-browser.py
  1# pico_synth_sandbox - Sample Browser Example
  2# 2023 Cooper Dalrymple - me@dcdalrymple.com
  3# GPL v3 License
  4
  5import gc, os
  6import pico_synth_sandbox.tasks
  7from pico_synth_sandbox import fftfreq
  8from pico_synth_sandbox.board import get_board
  9from pico_synth_sandbox.display import Display
 10from pico_synth_sandbox.encoder import Encoder
 11from pico_synth_sandbox.keyboard import get_keyboard_driver
 12from pico_synth_sandbox.audio import get_audio_driver
 13from pico_synth_sandbox.synth import Synth
 14from pico_synth_sandbox.voice.sample import Sample
 15import pico_synth_sandbox.waveform as waveform
 16
 17board = get_board()
 18
 19display = Display(board)
 20display.enable_horizontal_graph()
 21display.write("PicoSynthSandbox", (0,0))
 22display.write("Loading...", (0,1))
 23display.force_update()
 24
 25audio = get_audio_driver(board)
 26synth = Synth(audio)
 27synth.add_voices(Sample(loop=False) for i in range(4))
 28for voice in synth.voices:
 29    voice.set_envelope(
 30        attack_time=0.05,
 31        decay_time=0.1,
 32        release_time=0.0,
 33        attack_level=0.5,
 34        sustain_level=0.5
 35    )
 36
 37sample_data = None
 38sample_rate = audio.get_sample_rate()
 39sample_root = 440.0
 40
 41sample_files = list(filter(lambda x: x[-4:] == ".wav", os.listdir("/samples")))
 42if not sample_files:
 43    print("No samples available. Try running \"make samples --always-make\" in the library root directory.")
 44    exit()
 45
 46keyboard = get_keyboard_driver(board, root=60, max_voices=len(synth.voices))
 47def press(voice, notenum, velocity, keynum=None):
 48    synth.press(voice, notenum, velocity)
 49keyboard.set_voice_press(press)
 50def release(voice, notenum, keynum=None):
 51    synth.release(voice)
 52keyboard.set_voice_release(release)
 53
 54type = 0
 55semitone = 0
 56sample_index = 0
 57sample_index_loaded = -1
 58
 59def update_cursor():
 60    global type
 61    display.set_cursor_position(5 if type else 0, 0)
 62
 63def update_tune(write=True):
 64    global semitone
 65    for voice in synth.voices:
 66        voice.set_coarse_tune(semitone / 12.0)
 67    if write:
 68        display.write_horizontal_graph(semitone, -24, 24, (0,1), 4)
 69
 70def update_sample():
 71    global sample_index, sample_index_loaded
 72    display.write(sample_files[sample_index][:-4], (5,1))
 73    display.write("*" if sample_index == sample_index_loaded else " ", (15,0), 1)
 74
 75def increment_tune():
 76    global type, semitone
 77    if type != 0:
 78        type = 0
 79        update_cursor()
 80    if semitone < 24:
 81        semitone += 1
 82        update_tune()
 83def decrement_tune():
 84    global type, semitone
 85    if type != 0:
 86        type = 0
 87        update_cursor()
 88    if semitone > -24:
 89        semitone -= 1
 90        update_tune()
 91
 92def increment_sample():
 93    global type, sample_index, sample_files
 94    if type != 1:
 95        type = 1
 96        update_cursor()
 97    sample_index = (sample_index + 1) % len(sample_files)
 98    update_sample()
 99def decrement_sample():
100    global type, sample_index, sample_files
101    if type != 1:
102        type = 1
103        update_cursor()
104    sample_index = (sample_index - 1) % len(sample_files)
105    update_sample()
106
107def load_sample(write=True):
108    global semitone, sample_data, sample_rate, sample_root, sample_index, sample_index_loaded
109    if sample_index == sample_index_loaded:
110        return
111    
112    pico_synth_sandbox.tasks.pause()
113    audio.mute()
114    if write:
115        display.write("Loading...", (5,1))
116        display.force_update()
117
118    for voice in synth.voices:
119        voice.unload()
120    del sample_data
121    gc.collect()
122
123    semitone = 0
124    update_tune(write)
125
126    sample_data, sample_rate = waveform.load_from_file("/samples/" + sample_files[sample_index], max_samples=8192)
127    sample_root = fftfreq(
128        data=sample_data,
129        sample_rate=sample_rate
130    )
131    for voice in synth.voices:
132        voice.load(sample_data, sample_rate, sample_root)
133
134    sample_index_loaded = sample_index
135    if write: update_sample()
136    audio.unmute()
137    pico_synth_sandbox.tasks.resume()
138
139if board.num_encoders() == 1:
140
141    encoder = Encoder(board)
142
143    def next():
144        global type
145        if type == 0:
146            increment_tune()
147        else:
148            increment_sample()
149    encoder.set_increment(next)
150
151    def previous():
152        global type
153        if type == 0:
154            increment_tune()
155        else:
156            increment_sample()
157    encoder.set_decrement(previous)
158
159    def toggle():
160        global type
161        type = 0 if type else 1
162        update_cursor()
163    encoder.set_click(toggle)
164
165    encoder.set_long_press(load_sample)
166
167elif board.num_encoders() > 1:
168
169    encoder1 = Encoder(board, 0)
170    encoder1.set_increment(increment_tune)
171    encoder1.set_decrement(decrement_tune)
172
173    encoder2 = Encoder(board, 1)
174    encoder2.set_increment(increment_sample)
175    encoder2.set_decrement(decrement_sample)
176    encoder2.set_click(load_sample)
177
178display.clear()
179display.write("Tune Sample", position=(0,0))
180display.set_cursor_enabled(True)
181display.set_cursor_blink(True)
182update_tune()
183update_sample()
184
185pico_synth_sandbox.tasks.run()

Microphone Sample Example

Use the microphone to record a sample into memory when the encoder is long pressed. The sample is then loaded into a simple Sample voice to play it back with the touch keyboard. You can control the pitch and filter of the voice using the encoder. Use a short click of the encoder to switch between the two options.

examples/microphone-sample.py
  1# pico_synth_sandbox - Microphone Sample Example
  2# 2023 Cooper Dalrymple - me@dcdalrymple.com
  3# GPL v3 License
  4
  5import gc, time
  6import pico_synth_sandbox.tasks
  7from pico_synth_sandbox.tasks import Task
  8from pico_synth_sandbox import fftfreq, normalize
  9from pico_synth_sandbox.board import get_board
 10from pico_synth_sandbox.display import Display
 11from pico_synth_sandbox.encoder import Encoder
 12from pico_synth_sandbox.audio import get_audio_driver
 13from pico_synth_sandbox.synth import Synth
 14from pico_synth_sandbox.voice.sample import Sample
 15from pico_synth_sandbox.keyboard import get_keyboard_driver
 16from pico_synth_sandbox.microphone import Microphone
 17
 18board = get_board()
 19
 20display = Display(board)
 21display.enable_horizontal_graph()
 22display.write("PicoSynthSandbox", (0,0))
 23display.write("Loading...", (0,1))
 24display.force_update()
 25
 26audio = get_audio_driver(board)
 27synth = Synth(audio)
 28voice = Sample(loop=False)
 29synth.add_voice(voice)
 30voice.set_envelope(
 31    attack_time=0.05,
 32    decay_time=0.1,
 33    release_time=0.0,
 34    attack_level=0.75,
 35    sustain_level=0.75
 36)
 37voice.set_filter(
 38    type=Synth.FILTER_LPF,
 39    frequency=1.0,
 40    resonance=0.5,
 41    synth=synth
 42)
 43
 44keyboard = get_keyboard_driver(board, root=60, max_voices=1)
 45
 46def voice_press(index, notenum, velocity, keynum=None):
 47    synth.press(0, notenum, velocity)
 48keyboard.set_voice_press(voice_press)
 49
 50def voice_release(index, notenum, keynum=None):
 51    synth.release(0)
 52keyboard.set_voice_release(voice_release)
 53
 54microphone = Microphone(board)
 55
 56class MicrophoneLevel(Task):
 57    def __init__(self, microphone, update=None):
 58        self._microphone = microphone
 59        self._max_level = 0.0
 60        self._update = update
 61        Task.__init__(self, update_frequency=5)
 62    async def update(self):
 63        self._level = self._microphone.get_level()
 64        self._max_level = max(self._level, self._max_level)
 65        if self._update:
 66            self._update(self._level, self._max_level)
 67def update_level(level, max_level):
 68    if max_level > 0.0:
 69        display.write_horizontal_graph(level, 0.0, max_level, (12,1), 4)
 70    else:
 71        display.write("", (12,1), 4)
 72mic_level = MicrophoneLevel(microphone, update_level)
 73
 74def trigger():
 75    display.write("Recording")
 76    display.force_update()
 77microphone.set_trigger(trigger)
 78
 79type = 0
 80semitone = 0
 81filter = 100
 82
 83def update_tune(write=True):
 84    global semitone
 85    voice.set_coarse_tune(semitone / 12.0)
 86    if write:
 87        display.write_horizontal_graph(semitone, -24, 24, (0,1), 4)
 88
 89def update_filter(write=True):
 90    global filter
 91    voice.set_filter_frequency(filter / 100.0, synth)
 92    if write:
 93        display.write_horizontal_graph(filter, 0, 100, (5,1), 6)
 94
 95def update_cursor():
 96    global type
 97    display.set_cursor_position(5 if type else 0, 0)
 98
 99def reset_display():
100    display.clear()
101    display.write("Tune Filter Mic", (0,0))
102    update_tune()
103    update_filter()
104    update_cursor()
105    display.set_cursor_enabled(True)
106    display.set_cursor_blink(True)
107
108def start_record():
109    global semitone
110
111    pico_synth_sandbox.tasks.pause()
112    audio.mute()
113    display.set_cursor_enabled(False)
114    display.set_cursor_blink(False)
115    display.clear()
116
117    semitone = 0
118    update_tune(False)
119
120    voice.unload()
121    gc.collect()
122
123    display.write("Waiting")
124    display.force_update()
125
126    sample_data = microphone.read(
127        samples=4096,
128        trigger=0.01,
129        clip=0.001
130    )
131
132    display.write("Processing")
133    display.force_update()
134
135    # Normalize Volume
136    sample_data = normalize(sample_data)
137
138    # Calculate root frequency
139    sample_rate = Microphone.get_sample_rate()
140    sample_root = fftfreq(
141        data=sample_data,
142        sample_rate=sample_rate
143    )
144    voice.load(sample_data, sample_rate, sample_root)
145
146    display.clear()
147    display.write("Complete!")
148    display.force_update()
149    time.sleep(0.5)
150
151    reset_display()
152    audio.unmute()
153    pico_synth_sandbox.tasks.resume()
154
155def increment_semitone():
156    global type, semitone
157    if type != 0:
158        type = 0
159        update_cursor()
160    if semitone < 24:
161        semitone += 1
162        update_tune()
163def decrement_semitone():
164    global type, semitone
165    if type != 0:
166        type = 0
167        update_cursor()
168    if semitone > -24:
169        semitone -= 1
170        update_tune()
171
172def increment_filter():
173    global type, filter
174    if type != 1:
175        type = 1
176        update_cursor()
177    if filter < 100:
178        filter += 1
179        update_filter()
180def decrement_filter():
181    global type, filter
182    if type != 1:
183        type = 1
184        update_cursor()
185    if filter > 0:
186        filter -= 1
187        update_filter()
188
189if board.num_encoders() == 1:
190
191    encoder = Encoder(board)
192
193    def increment_encoder():
194        global type
195        if type == 0:
196            increment_semitone()
197        else:
198            increment_filter()
199    encoder.set_increment(increment_encoder)
200
201    def decrement_encoder():
202        global type
203        if type == 0:
204            increment_semitone()
205        else:
206            increment_filter()
207    encoder.set_decrement(decrement_encoder)
208
209    def toggle_encoder():
210        global type
211        type = 0 if type else 1
212        update_cursor()
213    encoder.set_click(toggle_encoder)
214
215    encoder.set_long_press(start_record)
216
217elif board.num_encoders() > 1:
218
219    encoder1 = Encoder(board, 0)
220    encoder1.set_increment(increment_semitone)
221    encoder1.set_decrement(decrement_semitone)
222    encoder1.set_long_press(start_record)
223
224    encoder2 = Encoder(board, 1)
225    encoder2.set_increment(increment_filter)
226    encoder2.set_decrement(decrement_filter)
227    encoder2.set_long_press(start_record)
228
229# Wait for microphone to initialize
230time.sleep(1)
231
232reset_display()
233
234pico_synth_sandbox.tasks.run()

Horizontal Bar Graph

Use custom characters to draw a horizontal bar on the lcd display. Useful for demonstrating parameter values. NOTE: vertical and horizontal bars cannot be displayed simultaneously, and display.enable_horizontal_graph must be called prior to display.write_horizontal_graph.

examples/display-bar-horizontal.py
 1# pico_synth_sandbox - Horizontal Bar Graph Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9
10board = get_board()
11
12display = Display(board)
13display.enable_horizontal_graph()
14
15value = 0
16def update_value():
17    global value
18    display.write(value, (0,0), 3, False)
19    display.write_horizontal_graph(
20        value=value,
21        minimum=0,
22        maximum=100,
23        position=(0,1),
24        width=16
25    )
26update_value()
27
28encoder = Encoder(board)
29def increment():
30    global value
31    if value < 100:
32        value += 1
33        update_value()
34encoder.set_increment(increment)
35def decrement():
36    global value
37    if value > 0:
38        value -= 1
39        update_value()
40encoder.set_decrement(decrement)
41
42pico_synth_sandbox.tasks.run()

Vertical Bar Graph

Use custom characters to draw a vertical bar on the lcd display. Useful for demonstrating levels. NOTE: vertical and horizontal bars cannot be displayed simultaneously, and display.enable_vertical_graph must be called prior to display.write_vertical_graph.

examples/display-bar-vertical.py
 1# pico_synth_sandbox - Vertical Bar Graph Example
 2# 2023 Cooper Dalrymple - me@dcdalrymple.com
 3# GPL v3 License
 4
 5import pico_synth_sandbox.tasks
 6from pico_synth_sandbox.board import get_board
 7from pico_synth_sandbox.display import Display
 8from pico_synth_sandbox.encoder import Encoder
 9
10board = get_board()
11
12display = Display(board)
13display.enable_vertical_graph()
14
15value = 0
16def update_value():
17    global value
18    display.write(value, (13,0), 3, True)
19    display.write_vertical_graph(
20        value=value,
21        minimum=0,
22        maximum=100,
23        position=(0,0),
24        height=2
25    )
26update_value()
27
28encoder = Encoder(board)
29def increment():
30    global value
31    if value < 100:
32        value += 1
33        update_value()
34encoder.set_increment(increment)
35def decrement():
36    global value
37    if value > 0:
38        value -= 1
39        update_value()
40encoder.set_decrement(decrement)
41
42pico_synth_sandbox.tasks.run()