from machine import Pin, I2C
import time
from lcd_api import LcdApi
from i2c_lcd import I2cLcd
# MIDI settings
MIDI_CHANNEL = 0 # MIDI channel 1 (0-indexed)
# Helper function to generate MIDI notes for a chord
def get_chord_notes(base_note, chord_type):
"""Generate MIDI notes for a chord based on its type."""
note_map = {"C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5,
"F#": 6, "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11}
base_midi = 60 + note_map[base_note] # Base note starting at MIDI note 60 (C4)
if chord_type == "Major":
return [base_midi, base_midi + 4, base_midi + 7]
elif chord_type == "Minor":
return [base_midi, base_midi + 3, base_midi + 7]
elif chord_type == "Diminished":
return [base_midi, base_midi + 3, base_midi + 6]
elif chord_type == "Major 7":
return [base_midi, base_midi + 4, base_midi + 7, base_midi + 11]
elif chord_type == "Minor 7":
return [base_midi, base_midi + 3, base_midi + 7, base_midi + 10]
elif chord_type == "Dominant 7":
return [base_midi, base_midi + 4, base_midi + 7, base_midi + 10]
return []
# Chord Database now includes all modes (Ionian, Dorian, Phrygian, etc.)
CHORD_DATABASE = {
"Ionian": {
"I": lambda base: get_chord_notes(base, "Major"),
"II": lambda base: get_chord_notes(base, "Minor 7"),
"III": lambda base: get_chord_notes(base, "Minor 7"),
"IV": lambda base: get_chord_notes(base, "Major 7"),
"V": lambda base: get_chord_notes(base, "Dominant 7"),
"VI": lambda base: get_chord_notes(base, "Minor 7"),
"VII": lambda base: get_chord_notes(base, "Diminished")
},
"Dorian": {
"I": lambda base: get_chord_notes(base, "Minor 7"),
"II": lambda base: get_chord_notes(base, "Minor 7"),
"III": lambda base: get_chord_notes(base, "Major 7"),
"IV": lambda base: get_chord_notes(base, "Major 7"),
"V": lambda base: get_chord_notes(base, "Dominant 7"),
"VI": lambda base: get_chord_notes(base, "Diminished"),
"VII": lambda base: get_chord_notes(base, "Major")
},
"Phrygian": {
"I": lambda base: get_chord_notes(base, "Minor 7"),
"II": lambda base: get_chord_notes(base, "Diminished"),
"III": lambda base: get_chord_notes(base, "Major 7"),
"IV": lambda base: get_chord_notes(base, "Minor 7"),
"V": lambda base: get_chord_notes(base, "Minor 7"),
"VI": lambda base: get_chord_notes(base, "Major"),
"VII": lambda base: get_chord_notes(base, "Dominant 7")
},
"Lydian": {
"I": lambda base: get_chord_notes(base, "Major 7"),
"II": lambda base: get_chord_notes(base, "Dominant 7"),
"III": lambda base: get_chord_notes(base, "Minor 7"),
"IV": lambda base: get_chord_notes(base, "Major"),
"V": lambda base: get_chord_notes(base, "Major 7"),
"VI": lambda base: get_chord_notes(base, "Minor"),
"VII": lambda base: get_chord_notes(base, "Diminished")
},
"Mixolydian": {
"I": lambda base: get_chord_notes(base, "Major"),
"II": lambda base: get_chord_notes(base, "Minor 7"),
"III": lambda base: get_chord_notes(base, "Diminished"),
"IV": lambda base: get_chord_notes(base, "Major 7"),
"V": lambda base: get_chord_notes(base, "Dominant 7"),
"VI": lambda base: get_chord_notes(base, "Minor"),
"VII": lambda base: get_chord_notes(base, "Minor 7")
},
"Aeolian": {
"I": lambda base: get_chord_notes(base, "Minor"),
"II": lambda base: get_chord_notes(base, "Diminished"),
"III": lambda base: get_chord_notes(base, "Major"),
"IV": lambda base: get_chord_notes(base, "Minor 7"),
"V": lambda base: get_chord_notes(base, "Minor 7"),
"VI": lambda base: get_chord_notes(base, "Major 7"),
"VII": lambda base: get_chord_notes(base, "Dominant 7")
},
"Locrian": {
"I": lambda base: get_chord_notes(base, "Diminished"),
"II": lambda base: get_chord_notes(base, "Minor 7"),
"III": lambda base: get_chord_notes(base, "Major"),
"IV": lambda base: get_chord_notes(base, "Minor"),
"V": lambda base: get_chord_notes(base, "Minor 7"),
"VI": lambda base: get_chord_notes(base, "Major 7"),
"VII": lambda base: get_chord_notes(base, "Dominant 7")
}
}
# User-selected base note and mode (default values)
BASE_NOTE = "C"
MODE = "Ionian"
BASE_NOTES = ["C", "D", "E", "F", "G", "A", "B"]
MODES = ["Ionian", "Dorian", "Phrygian", "Lydian", "Mixolydian", "Aeolian", "Locrian"]
# Rotary encoder pins for base note
ROTARY_BASE_A = 22
ROTARY_BASE_B = 21
rotary_base_a = Pin(ROTARY_BASE_A, Pin.IN, Pin.PULL_UP)
rotary_base_b = Pin(ROTARY_BASE_B, Pin.IN, Pin.PULL_UP)
base_note_index = BASE_NOTES.index(BASE_NOTE)
rotary_base_state = 0
# Rotary encoder pins for mode
ROTARY_MODE_A = 28
ROTARY_MODE_B = 27
rotary_mode_a = Pin(ROTARY_MODE_A, Pin.IN, Pin.PULL_UP)
rotary_mode_b = Pin(ROTARY_MODE_B, Pin.IN, Pin.PULL_UP)
mode_index = MODES.index(MODE)
rotary_mode_state = 0
# Buttons for selecting chords
CHORD_BUTTONS = [6, 7, 8, 9, 10, 11, 12] # GPIO pins for chords
chord_buttons = {pin: Pin(pin, Pin.IN, Pin.PULL_UP) for pin in CHORD_BUTTONS}
# Map buttons to chord progression indices
CHORD_INDICES = {6: "I", 7: "II", 8: "III", 9: "IV", 10: "V", 11: "VI", 12: "VII"}
# I2C LCD setup
I2C_SCL_PIN = 5
I2C_SDA_PIN = 4
I2C_ADDR = 0x27
I2C_ROWS = 2
I2C_COLS = 16
# Initialize I2C and LCD
i2c = I2C(0, scl=Pin(I2C_SCL_PIN), sda=Pin(I2C_SDA_PIN), freq=400000)
lcd = I2cLcd(i2c, I2C_ADDR, I2C_ROWS, I2C_COLS)
def update_lcd():
"""Update the LCD display with the current base note and mode."""
lcd.clear()
lcd.putstr(f"Note: {BASE_NOTE}\nMode: {MODE}")
# Function to send MIDI Note On message over serial
def send_note_on(note, velocity):
status = 0x90 | (MIDI_CHANNEL & 0x0F) # Note On message for the specified channel
print("Sending Note On:", [status, note, velocity])
# Function to send MIDI Note Off message over serial
def send_note_off(note, velocity):
status = 0x80 | (MIDI_CHANNEL & 0x0F) # Note Off message for the specified channel
print("Sending Note Off:", [status, note, velocity])
# Rotary encoder handling for base note
def check_rotary_base():
global rotary_base_state, base_note_index, BASE_NOTE
a_state = rotary_base_a.value()
b_state = rotary_base_b.value()
if a_state == 0 and b_state == 1 and rotary_base_state == 0:
rotary_base_state = 1
base_note_index = (base_note_index + 1) % len(BASE_NOTES)
BASE_NOTE = BASE_NOTES[base_note_index]
print(f"Base note set to {BASE_NOTE}")
update_lcd()
elif a_state == 1 and b_state == 0 and rotary_base_state == 0:
rotary_base_state = 1
base_note_index = (base_note_index - 1) % len(BASE_NOTES)
BASE_NOTE = BASE_NOTES[base_note_index]
print(f"Base note set to {BASE_NOTE}")
update_lcd()
elif a_state == 1 and b_state == 1:
rotary_base_state = 0
# Rotary encoder handling for mode
def check_rotary_mode():
global rotary_mode_state, mode_index, MODE
a_state = rotary_mode_a.value()
b_state = rotary_mode_b.value()
if a_state == 0 and b_state == 1 and rotary_mode_state == 0:
rotary_mode_state = 1
mode_index = (mode_index + 1) % len(MODES)
MODE = MODES[mode_index]
print(f"Mode set to {MODE}")
update_lcd()
elif a_state == 1 and b_state == 0 and rotary_mode_state == 0:
rotary_mode_state = 1
mode_index = (mode_index - 1) % len(MODES)
MODE = MODES[mode_index]
print(f"Mode set to {MODE}")
update_lcd()
elif a_state == 1 and b_state == 1:
rotary_mode_state = 0
# Debounce handling
last_chord_button_states = {pin: 1 for pin in CHORD_BUTTONS}
chord_pressed_states = {pin: False for pin in CHORD_BUTTONS}
# Initialize LCD with current values
update_lcd()
while True:
# Check rotary encoders
check_rotary_base()
check_rotary_mode()
# Check chord buttons
for pin, button in chord_buttons.items():
current_button_state = button.value()
if last_chord_button_states[pin] == 1 and current_button_state == 0: # Button pressed
chord_index = CHORD_INDICES[pin]
print(f"Chord button {pin} pressed, sending chord...")
# Get the notes for the selected chord from CHORD_DATABASE
chord_notes = CHORD_DATABASE[MODE][chord_index](BASE_NOTE)
# Send Note On messages for all notes in the chord
for note in chord_notes:
send_note_on(note, 127) # 127 is max velocity
elif last_chord_button_states[pin] == 0 and current_button_state == 1: # Button released
chord_index = CHORD_INDICES[pin]
print(f"Chord button {pin} released, stopping chord...")
# Get the notes for the selected chord from CHORD_DATABASE
chord_notes = CHORD_DATABASE[MODE][chord_index](BASE_NOTE)
# Send Note Off messages for all notes in the chord
for note in chord_notes:
send_note_off(note, 0) # 0 velocity for Note Off
# Update the last button state
last_chord_button_states[pin] = current_button_state
# Small delay for debounce
time.sleep(0.01)