|
| 1 | +# SPDX-FileCopyrightText: 2023 Liz Clark for Adafruit Industries |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | + |
| 5 | +import digitalio |
| 6 | +import audiobusio |
| 7 | +import board |
| 8 | +import neopixel |
| 9 | +import adafruit_lis3dh |
| 10 | +import synthio |
| 11 | +import keypad |
| 12 | +from adafruit_ticks import ticks_ms, ticks_add, ticks_diff |
| 13 | +import ulab.numpy as np |
| 14 | +from adafruit_seesaw.seesaw import Seesaw |
| 15 | +from adafruit_seesaw.neopixel import NeoPixel as SS_NeoPixel |
| 16 | +from adafruit_seesaw.digitalio import DigitalIO |
| 17 | +from adafruit_seesaw.rotaryio import IncrementalEncoder |
| 18 | +import audiomixer |
| 19 | +import busio |
| 20 | +import simpleio |
| 21 | + |
| 22 | +i2c = busio.I2C(board.SCL, board.SDA, frequency=800000) |
| 23 | + |
| 24 | +int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT) |
| 25 | +lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=int1) |
| 26 | +lis3dh.range = adafruit_lis3dh.RANGE_2_G |
| 27 | + |
| 28 | +ss_enc0 = Seesaw(i2c, addr=0x36) |
| 29 | +ss_enc0.pin_mode(24, ss_enc0.INPUT_PULLUP) |
| 30 | +button0 = DigitalIO(ss_enc0, 24) |
| 31 | +button0_state = False |
| 32 | +enc0 = IncrementalEncoder(ss_enc0) |
| 33 | +ss_enc0.set_GPIO_interrupts(1 << 24, True) |
| 34 | +ss_enc0.enable_encoder_interrupt(encoder=0) |
| 35 | + |
| 36 | +ss_enc1 = Seesaw(i2c, addr=0x37) |
| 37 | +ss_enc1.pin_mode(24, ss_enc1.INPUT_PULLUP) |
| 38 | +button1 = DigitalIO(ss_enc1, 24) |
| 39 | +button1_state = False |
| 40 | +enc1 = IncrementalEncoder(ss_enc1) |
| 41 | +ss_enc1.set_GPIO_interrupts(1 << 24, True) |
| 42 | +ss_enc1.enable_encoder_interrupt(encoder=0) |
| 43 | + |
| 44 | +ss_enc2 = Seesaw(i2c, addr=0x38) |
| 45 | +ss_enc2.pin_mode(24, ss_enc2.INPUT_PULLUP) |
| 46 | +button2 = DigitalIO(ss_enc2, 24) |
| 47 | +button2_state = False |
| 48 | +enc2 = IncrementalEncoder(ss_enc2) |
| 49 | +ss_enc2.set_GPIO_interrupts(1 << 24, True) |
| 50 | +ss_enc2.enable_encoder_interrupt(encoder=0) |
| 51 | + |
| 52 | +neokey0 = Seesaw(i2c, addr=0x30) |
| 53 | +neokey1 = Seesaw(i2c, addr=0x31) |
| 54 | + |
| 55 | +keys = [] |
| 56 | +for k in range(4, 8): |
| 57 | + key0 = DigitalIO(neokey0, k) |
| 58 | + key0.direction = digitalio.Direction.INPUT |
| 59 | + key0.pull = digitalio.Pull.UP |
| 60 | + keys.append(key0) |
| 61 | +for k in range(4, 8): |
| 62 | + key1 = DigitalIO(neokey1, k) |
| 63 | + key1.direction = digitalio.Direction.INPUT |
| 64 | + key1.pull = digitalio.Pull.UP |
| 65 | + keys.append(key1) |
| 66 | + |
| 67 | +NUM_PIXELS = 8 |
| 68 | +NEOPIXEL_PIN = board.EXTERNAL_NEOPIXELS |
| 69 | + |
| 70 | +pixels = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, auto_write=True) |
| 71 | + |
| 72 | +pixels.brightness = 0.1 |
| 73 | + |
| 74 | +enable = digitalio.DigitalInOut(board.EXTERNAL_POWER) |
| 75 | +enable.direction = digitalio.Direction.OUTPUT |
| 76 | +enable.value = True |
| 77 | +strum_switch = digitalio.DigitalInOut(board.D12) |
| 78 | +strum_switch.direction = digitalio.Direction.INPUT |
| 79 | +strum_switch.pull = digitalio.Pull.UP |
| 80 | + |
| 81 | +int_keys = keypad.Keys((board.D5, board.D6, board.D9, board.EXTERNAL_BUTTON), |
| 82 | + value_when_pressed=False, pull=True, interval = 0.001) |
| 83 | + |
| 84 | +key_pix0 = SS_NeoPixel(neokey0, 3, 4, auto_write = True) |
| 85 | +key_pix0.brightness = 1 |
| 86 | +key_pix1 = SS_NeoPixel(neokey1, 3, 4, auto_write = True) |
| 87 | +key_pix1.brightness = 1 |
| 88 | + |
| 89 | +key_pixels = [key_pix0[0], key_pix0[1], key_pix0[2], key_pix0[3], |
| 90 | + key_pix1[0], key_pix1[1], key_pix1[2], key_pix1[3]] |
| 91 | + |
| 92 | +# i2s audio |
| 93 | +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) |
| 94 | + |
| 95 | +key_states = [False, False, False, False, False, False, False, False] |
| 96 | +key_colors = [0xFF0000, 0xFF5500, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0x5500FF, 0xFF00FF] |
| 97 | +for c in range(4): |
| 98 | + key_pix0[c] = key_colors[c] |
| 99 | + key_pix1[c] = key_colors[c + 4] |
| 100 | + |
| 101 | +SAMPLE_RATE = 22050 |
| 102 | +SAMPLE_SIZE = 512 |
| 103 | +VOLUME = 32000 |
| 104 | + |
| 105 | +square = np.concatenate((np.ones(SAMPLE_SIZE//2, dtype=np.int16)*VOLUME,np.ones(SAMPLE_SIZE//2, |
| 106 | + dtype=np.int16)*-VOLUME)) |
| 107 | +sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, |
| 108 | + dtype=np.int16) |
| 109 | + |
| 110 | +amp_env = synthio.Envelope(attack_time=0.01, |
| 111 | + sustain_level=0.5, |
| 112 | + release_time=0.1) |
| 113 | + |
| 114 | +lfo_tremo = synthio.LFO(waveform=sine, rate=5) |
| 115 | + |
| 116 | +synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE) |
| 117 | + |
| 118 | +synth_notes = [] |
| 119 | + |
| 120 | +octave = 12 |
| 121 | +mult = 2 |
| 122 | +octave_range = 6 |
| 123 | +tones = [36, 40, 43, 47, 50, 53, 57, 60] |
| 124 | +diatonic = 0 |
| 125 | +t = [0, 0, 0, 0, 0, 0, 0, 0] |
| 126 | +current_freq = [] |
| 127 | +for s in range(8): |
| 128 | + t[s] = tones[s] + (octave * mult) |
| 129 | + print(t[s]) |
| 130 | + note = synthio.Note(frequency=synthio.midi_to_hz(t[s]), |
| 131 | + envelope=amp_env, waveform=square, |
| 132 | + amplitude = lfo_tremo) |
| 133 | + synth_notes.append(note) |
| 134 | + current_freq.append(synth_notes[s].frequency) |
| 135 | + |
| 136 | +lfo_frequency = 2000 |
| 137 | +lfo_resonance = 1.5 |
| 138 | +lpf = synth.low_pass_filter(lfo_frequency, lfo_resonance) |
| 139 | +hpf = synth.high_pass_filter(lfo_frequency, lfo_resonance) |
| 140 | + |
| 141 | +synth_volume = 0.3 |
| 142 | +last_pos0 = synth_volume |
| 143 | +last_pos1 = 0 |
| 144 | +last_pos2 = 0 |
| 145 | + |
| 146 | +mixer = audiomixer.Mixer(voice_count=1, sample_rate=SAMPLE_RATE, channel_count=1, |
| 147 | + bits_per_sample=16, samples_signed=True, buffer_size=2048) |
| 148 | +audio.play(mixer) |
| 149 | +mixer.voice[0].play(synth) |
| 150 | +mixer.voice[0].level = synth_volume |
| 151 | + |
| 152 | +int_number = 0 |
| 153 | +def normalize(val, min_v, max_v): |
| 154 | + return max(min(max_v, val), min_v) |
| 155 | + |
| 156 | +key_pressed = 0 |
| 157 | +strum = False |
| 158 | +last_strum = strum |
| 159 | +tremolo = True |
| 160 | +pressed_notes = [] |
| 161 | +last_y = 0 |
| 162 | +accel_time = 0.1 |
| 163 | +accel_clock = ticks_ms() |
| 164 | +accel_time = int(accel_time * 1000) |
| 165 | + |
| 166 | +while True: |
| 167 | + |
| 168 | + interrupt_event = int_keys.events.get() |
| 169 | + strum = strum_switch.value |
| 170 | + if last_strum != strum: |
| 171 | + synth.release_all() |
| 172 | + last_strum = strum |
| 173 | + if interrupt_event: |
| 174 | + int_number = interrupt_event.key_number |
| 175 | + if int_number == 0 and interrupt_event.pressed: |
| 176 | + pos0 = -enc0.position |
| 177 | + if pos0 != last_pos0: |
| 178 | + if pos0 > last_pos0: |
| 179 | + synth_volume = synth_volume + 0.1 |
| 180 | + else: |
| 181 | + synth_volume = synth_volume - 0.1 |
| 182 | + synth_volume = normalize(synth_volume, 0.0, 1.0) |
| 183 | + print(synth_volume) |
| 184 | + mixer.voice[0].level = synth_volume |
| 185 | + last_pos0 = pos0 |
| 186 | + if not button0.value and not button0_state: |
| 187 | + button0_state = True |
| 188 | + if mixer.voice[0].level > 0: |
| 189 | + mixer.voice[0].level = 0 |
| 190 | + else: |
| 191 | + mixer.voice[0].level = synth_volume |
| 192 | + if button0.value and button0_state: |
| 193 | + button0_state = False |
| 194 | + elif int_number == 1 and interrupt_event.pressed: |
| 195 | + pos1 = -enc1.position |
| 196 | + if pos1 != last_pos1: |
| 197 | + if pos1 > last_pos1: |
| 198 | + lfo_tremo.rate = lfo_tremo.rate + 0.5 |
| 199 | + else: |
| 200 | + lfo_tremo.rate = lfo_tremo.rate - 0.5 |
| 201 | + lfo_tremo.rate = normalize(lfo_tremo.rate, 1.0, 20.0) |
| 202 | + print(lfo_tremo.rate) |
| 203 | + last_pos1 = pos1 |
| 204 | + if tremolo: |
| 205 | + if not button1.value and not button1_state: |
| 206 | + button1_state = True |
| 207 | + tremolo = False |
| 208 | + for i in range(8): |
| 209 | + synth_notes[i].amplitude = 1.0 |
| 210 | + if button1.value and button1_state: |
| 211 | + button1_state = False |
| 212 | + else: |
| 213 | + if not button1.value and not button1_state: |
| 214 | + button1_state = True |
| 215 | + tremolo = True |
| 216 | + for i in range(8): |
| 217 | + lfo_tremo.rate = 5.0 |
| 218 | + synth_notes[i].amplitude = lfo_tremo |
| 219 | + if button1.value and button1_state: |
| 220 | + button1_state = False |
| 221 | + elif int_number == 2 and interrupt_event.pressed: |
| 222 | + pos2 = -enc2.position |
| 223 | + if pos2 != last_pos2: |
| 224 | + if pos2 > last_pos2: |
| 225 | + mult = (mult + 1) % octave_range |
| 226 | + print(mult) |
| 227 | + for o in range(8): |
| 228 | + t[o] = tones[o] + (octave * mult) |
| 229 | + print(t[o]) |
| 230 | + synth_notes[o].frequency = synthio.midi_to_hz(t[o]) |
| 231 | + current_freq[o] = synth_notes[o].frequency |
| 232 | + else: |
| 233 | + mult = (mult - 1) % octave_range |
| 234 | + print(mult) |
| 235 | + for o in range(8): |
| 236 | + t[o] = tones[o] + (octave * mult) |
| 237 | + print(t[o]) |
| 238 | + synth_notes[o].frequency = synthio.midi_to_hz(t[o]) |
| 239 | + current_freq[o] = synth_notes[o].frequency |
| 240 | + last_pos2 = pos2 |
| 241 | + if not button2.value and not button2_state: |
| 242 | + button2_state = True |
| 243 | + diatonic = (diatonic + 1) % 2 |
| 244 | + print(diatonic) |
| 245 | + if diatonic == 0: |
| 246 | + new_tones = [36, 40, 43, 47, 50, 53, 57, 60] |
| 247 | + for r in range(8): |
| 248 | + tones[r] = new_tones[r] |
| 249 | + print(tones[r]) |
| 250 | + else: |
| 251 | + new_tones = [36, 38, 40, 41, 43, 45, 47, 48] |
| 252 | + for r in range(8): |
| 253 | + tones[r] = new_tones[r] |
| 254 | + print(tones[r]) |
| 255 | + for x in range(8): |
| 256 | + t[x] = tones[x] + (octave * mult) |
| 257 | + print(t[x]) |
| 258 | + synth_notes[x].frequency = synthio.midi_to_hz(t[x]) |
| 259 | + current_freq[x] = synth_notes[x].frequency |
| 260 | + if button2.value and button2_state: |
| 261 | + button2_state = False |
| 262 | + elif int_number == 3 and interrupt_event.pressed: |
| 263 | + if strum: |
| 264 | + for i in range(0, 8): |
| 265 | + if not keys[i].value: |
| 266 | + pixels.fill(key_colors[i]) |
| 267 | + pixels.show() |
| 268 | + synth.press(synth_notes[i]) |
| 269 | + elif int_number == 3 and interrupt_event.released: |
| 270 | + if strum: |
| 271 | + synth.release_all() |
| 272 | + ss_enc0.get_GPIO_interrupt_flag() |
| 273 | + ss_enc1.get_GPIO_interrupt_flag() |
| 274 | + ss_enc2.get_GPIO_interrupt_flag() |
| 275 | + if ticks_diff(ticks_ms(), accel_clock) >= accel_time: |
| 276 | + x, y, z = [ |
| 277 | + value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration |
| 278 | + ] |
| 279 | + if last_y != y: |
| 280 | + if abs(last_y - y) > 0.01: |
| 281 | + # print(f"x = {x:.3f} G, y = {y:.3f} G, z = {z:.3f} G") |
| 282 | + if y < -0.500: |
| 283 | + mapped_freq = simpleio.map_range(y, -0.300, -1, 2000, 10000) |
| 284 | + mapped_resonance = simpleio.map_range(y, -0.300, -1, 1.5, 8) |
| 285 | + hpf = synth.high_pass_filter(mapped_freq, mapped_resonance) |
| 286 | + for i in range(0, 8): |
| 287 | + synth_notes[i].filter = hpf |
| 288 | + elif y > 0.200: |
| 289 | + mapped_freq = simpleio.map_range(y, 0.200, 1, 2000, 100) |
| 290 | + mapped_resonance = simpleio.map_range(y, 0.200, 1, 2, 0.5) |
| 291 | + lpf = synth.low_pass_filter(mapped_freq, mapped_resonance) |
| 292 | + for i in range(0, 8): |
| 293 | + synth_notes[i].filter = lpf |
| 294 | + else: |
| 295 | + for i in range(0, 8): |
| 296 | + synth_notes[i].filter = None |
| 297 | + last_y = y |
| 298 | + accel_clock = ticks_add(accel_clock, accel_time) |
| 299 | + if not strum: |
| 300 | + for i in range(0, 8): |
| 301 | + if not keys[i].value and not key_states[i]: |
| 302 | + pixels.fill(key_colors[i]) |
| 303 | + pixels.show() |
| 304 | + synth.press(synth_notes[i]) |
| 305 | + key_states[i] = True |
| 306 | + if keys[i].value and key_states[i]: |
| 307 | + key_pixels[i] = key_colors[i] |
| 308 | + synth.release(synth_notes[i]) |
| 309 | + key_states[i] = False |
0 commit comments