__version__ = "v1.1.0"
###########
# IMPORTS #
###########
# Board stuff
import board # type: ignore
import time
import os
# OLED stuff
import busio # type: ignore
import displayio # type: ignore
import adafruit_displayio_ssd1306
import terminalio
from adafruit_display_text import label
#########
# SETUP #
#########
# Constants
VELOCITY = 127
WIDTH = 128
HEIGHT = 32
BORDER = 1
# Environment
MIDI_CHANNEL = int(os.getenv("MIDI_CHANNEL", "0"))
j = os.getenv("MIDI_KEYMAPS", "")
MIDI_KEYMAPS = json.loads(j) if j else [{
0: 60, # C4
1: 61, # C#4
2: 62, # D4
3: 63, # D#4
4: 64, # E4
5: 65, # F4
6: 66, # F#4
7: 67, # G4,
}]
# OLED setup
OLED_SDA = board.GP0
OLED_SCL = board.GP1
i2c = busio.I2C(OLED_SCL, OLED_SDA)
displayio.release_displays()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT)
# Make the display context
splash = displayio.Group()
display.root_group = splash
color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF # White
# bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
# splash.append(bg_sprite)
# # Draw a smaller inner rectangle
# inner_bitmap = displayio.Bitmap(WIDTH - BORDER * 2, HEIGHT - BORDER * 2, 1)
# inner_palette = displayio.Palette(1)
# inner_palette[0] = 0x000000 # Black
# inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=BORDER, y=BORDER)
# splash.append(inner_sprite)
class MidiKeyboard():
def __init__(
self,
screen: displayio.Group,
midi_note_maps: list[dict]
):
"""Initialize the MidiKeyboard class
Args:
screen (displayio.Group): Group (splash/screen) to write to.
"""
self.midi_note_maps = midi_note_maps
self.layer = 0
self.locked = False
self.active_midi_notes = list()
self.screen = screen
self.currently_playing = label.Label(
terminalio.FONT,
text = "",
color = 0xFFFFFF,
x = 3,
y = HEIGHT // 2 - 1
)
self.screen.append(self.currently_playing)
self.locked_label = label.Label(
terminalio.FONT,
text = "",
color = 0xFFFFFF,
x = 35,
y = (HEIGHT // 3) + 4
)
self.screen.append(self.locked_label)
self.layer_label = label.Label(
terminalio.FONT,
text = str(self.layer),
color = 0xFFFFFF,
x = 35,
y = (HEIGHT // 3) * 2 + 4
)
self.screen.append(self.layer_label)
self.sw_cc_label = label.Label(
terminalio.FONT,
text = "CC1 ",
color = 0xFFFFFF,
x = 72,
y = (HEIGHT // 3) * 1 + 4
)
self.screen.append(self.sw_cc_label)
self.enc_cc_label = label.Label(
terminalio.FONT,
text = "CC2 ",
color = 0xFFFFFF,
x = 72,
y = (HEIGHT // 3) * 2 + 4
)
self.screen.append(self.enc_cc_label)
self.update_oled()
def update_oled(self) -> None:
self.currently_playing.text = self._get_note_name(self.active_midi_notes[-1]) if len(self.active_midi_notes) > 0 else ""
self.layer_label.text = str(self.layer)
self.locked_label.text = "X" if self.locked else "O"
def _get_note_name(self, note_number: int) -> str:
"""Convert a MIDI note number to its corresponding note name.
This helper function was generated by Gemini.
Args:
note_number (int): Note number to be converted
Returns:
str: formatted note name
"""
note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
octave = (note_number // 12) - 1 # MIDI note 0 is C-1, so C4 is octave 4
note_in_octave = note_number % 12
return f"{note_names[note_in_octave]}{octave}"
def outline_screen(screen: displayio.Group) -> None:
"""Draw a white outline around the screen with a black interior.
Drawing a white border around the screen helps the display stand out on the
Macropad. The black interior provides a clean background for the controls and
text to be displayed on.
Args:
screen (displayio.Group): Group (splash/screen) to write to.
"""
color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF
bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
screen.append(bg_sprite)
inner_bitmap = displayio.Bitmap(WIDTH - BORDER * 2, HEIGHT - BORDER * 2, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000
inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=BORDER, y=BORDER)
screen.append(inner_sprite)
def splash_screen(screen: displayio.Group, text: str) -> None:
"""Display a splash screen with a given text.
Animate the text in from left to right, then out again, for a typewriter effect.
Args:
screen (displayio.Group): Group (splash/screen) to write to.
text (str): The text to display on the splash screen.
"""
text_area = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=32, y=HEIGHT // 2 - 1)
screen.append(text_area)
for i in range(len(text) + 1):
text_area.text = text[0:i]
time.sleep(0.25)
time.sleep(2)
for i in range(len(text) + 1):
text_area.text = " " * i + text[i:len(text)]
time.sleep(0.25)
screen.remove(text_area)
# m = MidiKeyboard(screen=splash, midi_note_maps=MIDI_KEYMAPS)
keyboard = MidiKeyboard(
screen=splash,
row_pins=ROW_PINS,
midi_note_maps=MIDI_KEYMAPS,
cc_1=64,
cc_2=7,
cc_1_default=0,
cc_2_default=127,
)
if __name__ == '__main__':
# outline_screen(screen=splash)
# splash_screen(screen=splash, text=f"HackPiano {__version__[0:2]}")
# time.sleep(3)
m.layer = 1
m.active_midi_notes.append(64)
# m.sustain = True
m.update_oled()
time.sleep(2)
m.layer = -12
m.active_midi_notes.append(3)
# m.sustain = False
m.update_oled()
time.sleep(2)
m.layer = 71
m.active_midi_notes.remove(64)
# m.sustain = True
m.update_oled()
time.sleep(1)
m.active_midi_notes.remove(3)
m.update_oled()
while True:
pass