Skip to content

Instantly share code, notes, and snippets.

@tannewt
Created October 25, 2024 17:30
Show Gist options
  • Save tannewt/9a3bfc09323e0890806993e6af0edfa1 to your computer and use it in GitHub Desktop.
Save tannewt/9a3bfc09323e0890806993e6af0edfa1 to your computer and use it in GitHub Desktop.
midi to gameboy
import busio
import microcontroller
import time
import random
import _gbio
import adafruit_midi
# TimingClock is worth importing first if present as it
# will make parsing more efficient for this high frequency event
# Only importing what is used will save a little bit of memory
from adafruit_midi.timing_clock import TimingClock
# from adafruit_midi.channel_pressure import ChannelPressure
from adafruit_midi.control_change import ControlChange
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
# from adafruit_midi.pitch_bend_change import PitchBendChange
# from adafruit_midi.polyphonic_key_pressure import PolyphonicKeyPressure
# from adafruit_midi.program_change import ProgramChange
# from adafruit_midi.start import Start
# from adafruit_midi.stop import Stop
# from adafruit_midi.system_exclusive import SystemExclusive
from adafruit_midi.midi_message import MIDIUnknownEvent
_gbio.reset_gameboy()
midi_out = busio.UART(microcontroller.pin.PB31, None, baudrate=31250)
midi_in = busio.UART(None, microcontroller.pin.PA00, baudrate=31250, timeout=0)
# for i in range(10):
# midi_out.write(str(i).encode("utf-8"))
# print(midi_in.read())
# time.sleep(0.5)
# midi_inoutdemo - demonstrates receiving and sending MIDI events
midi = adafruit_midi.MIDI(midi_in=midi_in, midi_out=midi_out, in_channel=(0,1,2,3), out_channel=0)
print("Midi Demo in and out")
# Convert channel numbers at the presentation layer to the ones musicians use
print("Default output channel:", midi.out_channel + 1)
print("Listening on input channels:", tuple([c + 1 for c in midi.in_channel]))
import array
pitch_bits = array.array("B", [0]) * (128 * 2)
for i in range(128):
f = 440 * 2 ** ((i-60+16) / 12)
gb = 2048 - 131072 / f
gb = max(0, int(round(gb)))
pitch_bits[i * 2] = gb >> 8
pitch_bits[i * 2 + 1] = gb & 0xff
on = ( b"\x00\x0e\x13" # Load 0x13 into C Increment fails for some reason
#b"\x0c" # increment C
b"\x3e\x83" # Load 0x83 into A
b"\xe2" # Loads A into ff00 + C (0x13)
b"\x0c" # increment C
b"\x3e\x87" # Load 0x87 into A
b"\xe2") # Loads A into ff00 + C (0x14)
current_note_bits = [0, 0]
def note_on(midi_note, *, voice=0):
instructions = bytearray(on)
# volume is slow to respond per note
# instructions[7] = 0x03 | volume << 4
if voice == 1:
instructions[2] = 0x18
instructions[4] = pitch_bits[midi_note * 2 + 1]
instructions[8] = 0x80 | pitch_bits[midi_note * 2]
current_note_bits[voice] = pitch_bits[midi_note * 2]
gbio.queue_commands(instructions)
def note_off(midi_note, *, voice=0):
instructions = bytearray(b"\x00\x0e\x12" # Load 0x13 into C Increment fails for some reason
b"\x3e\x00" # Load 0x83 into A
b"\xe2") # Loads A into ff00 + C (0x13)
if voice == 1:
instructions[2] = 0x19
#instructions[4] |= current_note_bits[voice]
#current_note_bits[voice] = 0
gbio.queue_commands(instructions)
voice_settings = ( b"\x00\x0e\x11" # Load 0x11 into C Increment fails for some reason
b"\x3e\x5f" # Load 0x3f into A
b"\xe2" # Loads A into ff00 + C (0x16)
b"\x0c" # increment C
b"\x3e\x00" # Load 0x87 into A
b"\xe2" # Loads A into ff00 + C (0x17)
# Voice 2
b"\x0e\x16" # Load 0x16 into C Increment fails for some reason
b"\x3e\x5f" # Load 0x3f into A
b"\xe2" # Loads A into ff00 + C (0x16)
b"\x0c" # increment C
b"\x3e\xf0" # Load 0x87 into A
b"\xe2") # Loads A into ff00 + C (0x17)
_gbio.queue_commands(voice_settings)
voice_state = [0, 0]
while True:
message = midi.receive() # non-blocking read
if not message:
continue
print(message)
# For a Note On or Note Off play a major chord
# For any other known event just forward it
if isinstance(msg_in, NoteOn) and msg_in.velocity != 0:
# print("On", msg_in.note,
# "from channel", channel_in + 1)
if 0 in voice_state:
free_voice = voice_state.index(0)
note_on(msg_in.note, voice=free_voice)
voice_state[free_voice] = msg_in.note
else:
# Busy, pass it on.
midi.send(msg_in)
elif (isinstance(msg_in, NoteOff) or
isinstance(msg_in, NoteOn) and msg_in.velocity == 0):
# print("Off", msg_in.note,
# "from channel", channel_in + 1)
if msg_in.note in voice_state:
active_voice = voice_state.index(msg_in.note)
note_off(msg_in.note, voice=active_voice)
voice_state[active_voice] = 0
else:
# We're not playing it, pass it on.
midi.send(msg_in)
# print(voice_state)
elif isinstance(msg_in, MIDIUnknownEvent):
# Message are only known if they are imported
print("Unknown MIDI event status", msg_in.status)
elif isinstance(msg_in, ControlChange):
# Message are only known if they are imported
print("Control change", msg_in.control, msg_in.value)
elif isinstance(msg_in, TimingClock):
pass
elif msg_in is not None:
print(msg_in)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment