To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory MIDI_Melody_Maker/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
CIRCUITPY

# SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import time from random import randint import board import simpleio import busio import terminalio import neopixel from digitalio import DigitalInOut, Direction, Pull from analogio import AnalogIn import displayio import adafruit_imageload from adafruit_display_text import label import adafruit_displayio_ssd1306 # uncomment if using USB MIDI # import usb_midi from adafruit_display_shapes.rect import Rect import adafruit_midi from adafruit_midi.note_on import NoteOn from adafruit_midi.note_off import NoteOff from adafruit_midi.control_change import ControlChange displayio.release_displays() oled_reset = board.D9 #turn off on-board neopixel pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0) pixel.fill((0, 0, 0)) # Use for I2C for STEMMA OLED i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller display_bus = displayio.I2CDisplay(i2c, device_address=0x3D, reset=oled_reset) # STEMMA OLED dimensions. can have height of 64, but 32 makes text larger WIDTH = 128 HEIGHT = 32 BORDER = 0 # blinka sprite indexes EMPTY = 0 BLINKA_1 = 1 BLINKA_2 = 2 # setup for STEMMA OLED display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT) # create the displayio object splash = displayio.Group() display.root_group = splash # text for BPM bpm_text = "BPM: " bpm_text_area = label.Label( terminalio.FONT, text=bpm_text, color=0xFFFFFF, x=4, y=6 ) splash.append(bpm_text_area) bpm_rect = Rect(0, 0, 50, 16, fill=None, outline=0xFFFFFF) splash.append(bpm_rect) # text for key key_text = "Key: " key_text_area = label.Label( terminalio.FONT, text=key_text, color=0xFFFFFF, x=4, y=21 ) splash.append(key_text_area) key_rect = Rect(0, 15, 50, 16, fill=None, outline=0xFFFFFF) splash.append(key_rect) # text for mode mode_text = "Mode: " mode_text_area = label.Label( terminalio.FONT, text=mode_text, color=0xFFFFFF, x=54, y=21 ) splash.append(mode_text_area) mode_rect = Rect(50, 15, 78, 16, fill=None, outline=0xFFFFFF) splash.append(mode_rect) # text for beat division beat_text = "Div: " beat_text_area = label.Label( terminalio.FONT, text=beat_text, color=0xFFFFFF, x=54, y=6 ) splash.append(beat_text_area) beat_rect = Rect(50, 0, 78, 16, fill=None, outline=0xFFFFFF) splash.append(beat_rect) # Blinka sprite setup blinka, blinka_pal = adafruit_imageload.load("/spritesWhite.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette) # creates a transparent background for Blinka blinka_pal.make_transparent(7) blinka_grid = displayio.TileGrid(blinka, pixel_shader=blinka_pal, width=1, height=1, tile_height=16, tile_width=16, default_tile=EMPTY) blinka_grid.x = 112 blinka_grid.y = 0 splash.append(blinka_grid) # imports MIDI # USB MIDI: # midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) # UART MIDI: midi = adafruit_midi.MIDI(midi_out=busio.UART(board.TX, board.RX, baudrate=31250, timeout=0.001), out_channel=0) # potentiometer pin setup key_pot = AnalogIn(board.A1) mode_pot = AnalogIn(board.A2) beat_pot = AnalogIn(board.A3) bpm_slider = AnalogIn(board.A4) mod_pot = AnalogIn(board.A5) # run switch setup run_switch = DigitalInOut(board.D5) run_switch.direction = Direction.INPUT run_switch.pull = Pull.UP # arrays of notes in each key key_of_C = [60, 62, 64, 65, 67, 69, 71, 72] key_of_Csharp = [61, 63, 65, 66, 68, 70, 72, 73] key_of_D = [62, 64, 66, 67, 69, 71, 73, 74] key_of_Dsharp = [63, 65, 67, 68, 70, 72, 74, 75] key_of_E = [64, 66, 68, 69, 71, 73, 75, 76] key_of_F = [65, 67, 69, 70, 72, 74, 76, 77] key_of_Fsharp = [66, 68, 70, 71, 73, 75, 77, 78] key_of_G = [67, 69, 71, 72, 74, 76, 78, 79] key_of_Gsharp = [68, 70, 72, 73, 75, 77, 79, 80] key_of_A = [69, 71, 73, 74, 76, 78, 80, 81] key_of_Asharp = [70, 72, 74, 75, 77, 79, 81, 82] key_of_B = [71, 73, 75, 76, 78, 80, 82, 83] # array of keys keys = [key_of_C, key_of_Csharp, key_of_D, key_of_Dsharp, key_of_E, key_of_F, key_of_Fsharp, key_of_G, key_of_Gsharp, key_of_A, key_of_Asharp, key_of_B] # array of note indexes for modes fifths = [0, 4, 3, 7, 2, 6, 4, 7] major = [4, 2, 0, 3, 5, 7, 6, 4] minor = [5, 7, 2, 4, 6, 5, 1, 3] pedal = [5, 5, 5, 6, 5, 5, 5, 7] # defining variables for key name strings C_name = "C" Csharp_name = "C#" D_name = "D" Dsharp_name = "D#" E_name = "E" F_name = "F" Fsharp_name = "F#" G_name = "G" Gsharp_name = "G#" A_name = "A" Asharp_name = "A#" B_name = "B" # array of strings for key names for use with the display key_names = [C_name, Csharp_name, D_name, Dsharp_name, E_name, F_name, Fsharp_name, G_name, Gsharp_name, A_name, Asharp_name, B_name] # function for reading analog inputs def val(voltage): return voltage.value # comparitors for pots' values mod_val2 = 0 beat_val2 = 0 bpm_val2 = 120 key_val2 = 0 mode_val2 = 0 # time.monotonic for running the modes run = 0 # state for being on/off run_state = False # indexes for modes r = 0 b = 0 f = 0 p = 0 maj = 0 mi = 0 random = 0 # mode states play_pedal = False play_fifths = False play_maj = False play_min = False play_rando = False play_scale = True # state for random beat division rando = False # comparitors for states last_r = 0 last_f = 0 last_maj = 0 last_min = 0 last_p = 0 last_random = 0 # index for random beat division hit = 0 # default tempo tempo = 60 # beat division sixteenth = 15 / tempo eighth = 30 / tempo quarter = 60 / tempo half = 120 / tempo whole = 240 / tempo # time.monotonic for blinka animation slither = 0 # blinka animation sprite index g = 1 # array for random beat division values rando_div = [240, 120, 60, 30, 15] # array of beat division values beat_division = [whole, half, quarter, eighth, sixteenth] # strings for beat division names beat_division_name = ["1", "1/2", "1/4", "1/8", "1/16", "Random"] while True: # mapping analog pot values to the different parameters # MIDI modulation 0-127 mod_val1 = round(simpleio.map_range(val(mod_pot), 0, 65535, 0, 127)) # BPM range 60-220 bpm_val1 = simpleio.map_range(val(bpm_slider), 0, 65535, 60, 220) # 6 options for beat division beat_val1 = round(simpleio.map_range(val(beat_pot), 0, 65535, 0, 5)) # 12 options for key selection key_val1 = round(simpleio.map_range(val(key_pot), 0, 65535, 0, 11)) # 6 options for mode selection mode_val1 = round(simpleio.map_range(val(mode_pot), 0, 65535, 0, 5)) # sending MIDI modulation if abs(mod_val1 - mod_val2) > 2: # updates previous value to hold current value mod_val2 = mod_val1 # MIDI data has to be sent as an integer # this converts the pot data into an int modulation = int(mod_val2) # int is stored as a CC message modWheel = ControlChange(1, modulation) # CC message is sent midi.send(modWheel) print(modWheel) # delay to settle MIDI data time.sleep(0.001) # sets beat division if abs(beat_val1 - beat_val2) > 0: # updates previous value to hold current value beat_val2 = beat_val1 print("beat div is", beat_val2) # updates display beat_text_area.text = "Div:%s" % beat_division_name[beat_val2] # sets random beat division state if beat_val2 == 5: rando = True else: rando = False time.sleep(0.001) # mode selection if abs(mode_val1 - mode_val2) > 0: # updates previous value to hold current value mode_val2 = mode_val1 # scale mode if mode_val2 == 0: play_scale = True play_maj = False play_min = False play_fifths = False play_pedal = False play_rando = False # updates display mode_text_area.text = "Mode:Scale" print("scale") # major triads mode if mode_val2 == 1: play_scale = False play_maj = True play_min = False play_fifths = False play_pedal = False play_rando = False print("major chords") # updates display mode_text_area.text = "Mode:MajorTriads" # minor triads mode if mode_val2 == 2: play_scale = False play_maj = False play_min = True play_fifths = False play_pedal = False play_rando = False print("minor") # updates display mode_text_area.text = "Mode:MinorTriads" # fifths mode if mode_val2 == 3: play_scale = False play_maj = False play_min = False play_fifths = True play_pedal = False play_rando = False print("fifths") # updates display mode_text_area.text = "Mode:Fifths" # pedal tone mode if mode_val2 == 4: play_scale = False play_maj = False play_min = False play_fifths = False play_pedal = True play_rando = False print("play random") # updates display mode_text_area.text = 'Mode:Pedal' # random mode if mode_val2 == 5: play_scale = False play_maj = False play_min = False play_fifths = False play_pedal = False play_rando = True print("play random") # updates display mode_text_area.text = 'Mode:Random' time.sleep(0.001) # key selection if abs(key_val1 - key_val2) > 0: # updates previous value to hold current value key_val2 = key_val1 # indexes the notes in each key array for k in keys: o = keys.index(k) octave = keys[o] # updates display key_text_area.text = 'Key:%s' % key_names[key_val2] print("o is", o) time.sleep(0.001) # BPM adjustment if abs(bpm_val1 - bpm_val2) > 1: # updates previous value to hold current value bpm_val2 = bpm_val1 # updates tempo tempo = int(bpm_val2) # updates calculations for beat division sixteenth = 15 / tempo eighth = 30 / tempo quarter = 60 / tempo half = 120 / tempo whole = 240 / tempo # updates array of beat divisions beat_division = [whole, half, quarter, eighth, sixteenth] # updates display bpm_text_area.text = "BPM:%d" % tempo print("tempo is", tempo) time.sleep(0.05) # if the run switch is pressed: if run_switch.value: run_state = True # if random beat division, then beat_division index is randomized with index hit if rando: divide = beat_division[hit] # if not random, then beat_division is the value of the pot else: divide = beat_division[beat_val2] # blinka animation in time with BPM and beat division # she will slither every time a note is played if (time.monotonic() - slither) >= divide: blinka_grid[0] = g g += 1 slither = time.monotonic() if g > 2: g = 1 # holds key index octave = keys[key_val2] # fifths mode if play_fifths: # tracks time divided by the beat division if (time.monotonic() - run) >= divide: # note index from mode, r counts index position f = fifths[r] # sends NoteOn midi.send(NoteOn(octave[f])) # turns previous note off midi.send(NoteOff(octave[last_f])) # print(octave[r]) run = time.monotonic() # go to next note r += 1 # updates previous value to hold current value if r > 0: last_r = r last_f = f hit = randint(2, 4) # resets note index position if r > 7: r = 0 last_r = r last_f = f hit = randint(2, 4) # major triad mode if play_maj: # tracks time divided by the beat division if (time.monotonic() - run) >= divide: # note index from mode, r counts index position maj = major[r] # sends NoteOn midi.send(NoteOn(octave[maj])) # turns previous note off midi.send(NoteOff(octave[last_maj])) # print(octave[r]) run = time.monotonic() # go to next note r += 1 # updates previous value to hold current value if r > 0: last_r = r last_maj = maj hit = randint(2, 4) # resets note index position if r > 7: r = 0 last_r = r last_maj = maj hit = randint(2, 4) # minor triad mode if play_min: # tracks time divided by the beat division if (time.monotonic() - run) >= divide: # note index from mode, r counts index position mi = minor[r] # sends NoteOn midi.send(NoteOn(octave[mi])) # turns previous note off midi.send(NoteOff(octave[last_min])) # print(octave[r]) run = time.monotonic() # go to next note r += 1 # updates previous value to hold current value if r > 0: last_r = r last_min = mi hit = randint(2, 4) # resets note index position if r > 7: r = 0 last_r = r last_min = mi hit = randint(2, 4) # pedal tone mode if play_pedal: # tracks time divided by the beat division if (time.monotonic() - run) >= divide: # note index from mode, r counts index position p = pedal[r] # sends NoteOn midi.send(NoteOn(octave[p])) # turns previous note off midi.send(NoteOff(octave[last_p])) # print(octave[r]) run = time.monotonic() # go to next note r += 1 # updates previous value to hold current value if r > 0: last_r = r last_p = p hit = randint(2, 4) # resets note index position if r > 7: r = 0 last_r = r last_p = p hit = randint(2, 4) # random note mode if play_rando: # randomizes note indexes in key r = randint(0, 7) # tracks time divided by the beat division if (time.monotonic() - run) >= divide: # sends NoteOn midi.send(NoteOn(octave[r])) # turns previous note off midi.send(NoteOff(octave[last_r])) # print(octave[r]) run = time.monotonic() # updates previous value to hold current value if r > 0: last_r = r r = randint(0, 7) hit = randint(2, 4) # scale mode if play_scale: # tracks time divided by the beat division if (time.monotonic() - run) >= divide: # sends NoteOn midi.send(NoteOn(octave[r])) # turns previous note off midi.send(NoteOff(octave[last_r])) # print(octave[r]) run = time.monotonic() # go to next note r += 1 # updates previous value to hold current value if r > 0: last_r = r hit = randint(2, 4) # resets note index position if r > 7: r = 0 last_r = r if not run_switch.value: if run_state: all_note_off = ControlChange(123, 0) # CC message is sent midi.send(all_note_off) run_state = False time.sleep(0.001) # delay to settle MIDI data time.sleep(0.005)
There is a bitmap sprite sheet, spritesWhite.bmp, that is also needed for the code. It will allow for an animation to play on the OLED of Blinka slithering along with the beat of your melodies.
Page last edited January 21, 2025
Text editor powered by tinymce.