Created
October 25, 2024 17:30
-
-
Save tannewt/9a3bfc09323e0890806993e6af0edfa1 to your computer and use it in GitHub Desktop.
midi to gameboy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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