# EMF Camp Tilda Mk 3 Metronome

# Copy file to board from Windows Command line:
# e.g. >copy main.py e:\main.py /Y

# To run without reboot connect to com port of card at 115200 BAUD
# and run command:
# exec(open("main.py").read())

# Keys:
# - A/B : Move up and down through the number of steps in the bar and the strong/weak steps pattern
# - Menu: Change the number of steps in each beat (shown by the grey lines)
# - Joy Centre: Start/stop
# - Joy up/down: change BPM
# - Joy left: change light flash on start of bar/strong beats
# - Joy right: step forward one step


import ugfx
import buttons
import pyb
from pyb import UART

#modeNames = ["2","3","4","4 (2+2)","6","6 (3+3)", "6 (2+2+2)","8","8 (4+4)"]
bpmNames = [ 
[0, "Larghissimo"], 
[25,"Grave"], 
[41,"Largo"],
[61,"Larghetto"],
[67,"Adagio"],
[73,"Adagietto"],
[77,"Andante"],
[81,"Andantino"],
[93,"Andante moderato"],
[109,"Moderato"],
[113,"Allegretto"],
[121,"Allegro"],
[169,"Vivace"],
[173,"Vivacissimo"],
[177,"Presto"],
[200,"Prestissimo"],
[1000,"Blah"]
]
modeLens = [ 2, 3, 4, 4, 6, 6, 6, 8, 8, 8] # Number of steps
modeMods = [ 2, 3, 4, 2, 6, 3, 2, 8, 4, 2] # Where the strong beats fall
modeClks = [24,24,24,24,24,24,24,12,12, 12] # Number of midi clocks per step
bpmLens = [[],[1,2],[1,3],[1,2,4],[],[1,2,3,6],[],[1,2,4,8]] # Allowed values of steps per beat
bpms = list(range(20,40))+[40,42,44,46,48,50,52,54,56,58,60,63,66,69,72,76,80,84,88,92,96,100,104,108,112,116,120,126,132,138,144,152,160,168,176,184,192,200,208]
numModes = len(modeLens)
numBpms = len(bpms)

def resetSleep():
    global sleepStart
    sleepStart = pyb.millis()
    ugfx.backlight(100)
    
def getBpmName(bpm):
    for i in range(1,len(bpmNames)):
        if bpm < bpmNames[i][0]:
            return bpmNames[i-1][1]
            
def showBpm():
    ugfx.area(0,0,276,45,ugfx.BLACK)
    ugfx.set_default_font(ugfx.FONT_TITLE)
    ugfx.text(0,10,str(bpms[bpmp]) +" "+getBpmName(bpms[bpmp]) ,ugfx.WHITE)
    
def showPlay():
    ugfx.area(276,0,44,45,ugfx.BLACK)
    if playOn:
        ugfx.fill_circle(298,22,15, ugfx.GREEN)
    else:
        ugfx.circle(298,22,15, ugfx.RED)
        
def getCol(n):
    if (n%modeMods[metMode])==0:
        return ugfx.WHITE
    else:
        return ugfx.RED
        
def fillRect(i,c):
    ugfx.area(40*i+3,101,34,98,c)
    
def showG():
    ugfx.area(0,46,320,240-45,ugfx.BLACK)
    for i in range(modeLens[metMode]):
        ugfx.box(40*i+2,100,36,100,getCol(i))
    fillRect(pos,getCol(pos))
    for i in range(modeLens[metMode]/stepsPerBpmUnit):
        ugfx.area(40*i*stepsPerBpmUnit+6,206,stepsPerBpmUnit*40-12, 4, ugfx.WHITE)
        
def updateMode(c):
    global metMode, pos, stepsPerBpmUnit
    oldLen = modeLens[metMode]
    metMode = metMode + c
    if metMode < 0:
        metMode = 0
    if metMode >= numModes:
        metMode = numModes-1
    pos = modeLens[metMode]-1
    if (oldLen != modeLens[metMode]):
        stepsPerBpmUnit = 1
    showBpm()
    showG()
    
def playBeat(b):
    uart.writechar(0x99)
    if b == 0 :
       uart.writechar(0x24)
    elif (b % modeMods[metMode]) == 0:
       uart.writechar(0x26)
    else:
       uart.writechar(0x2a)
    uart.writechar(0x7f)
    beatStart = pyb.millis()
    for i in range(modeClks[metMode]):
        uart.writechar(0xf8)
    
def nextBeat():
    global beatStart, pos, ledStart
    beatStart = pyb.millis()
    fillRect(pos,ugfx.BLACK)
    pos = (pos + 1) % modeLens[metMode]
    playBeat(pos)
    if flashMode > 0:
        if pos == 0:
            ledStart = pyb.millis()
            pyb.LED(1).on()
            pyb.LED(2).on()
        elif flashMode == 2 and (pos % modeMods[metMode]) == 0:
            ledStart = pyb.millis()
            pyb.LED(1).on()
    fillRect(pos,getCol(pos))

def nextBpmStep():
    global stepsPerBpmUnit
    s = bpmLens[modeLens[metMode]-1] # Find the list of possible stepsperBPM
    p = s.index(stepsPerBpmUnit)
    p = p+1
    if p == len(s):
        p = 0
    stepsPerBpmUnit = s[p]
    showG()


metMode=2
bpmp=int(numBpms/2)+1
pos = modeLens[metMode]-1
zeroInterval = 50
firstInterval = 400
secondInterval = 60
sleepInterval = 1000*60*30 # 30 minutes
preSleepInterval = int(sleepInterval*3/4)
pedalInterval = 50
pedalBounceFlag = False
pedalGoneUp = True
upStart = pyb.millis()
downStart = pyb.millis()
upInterval = zeroInterval
upCount = 0
downInterval = zeroInterval
downCount = 0
beatStart = pyb.millis()
playOn = False
ledStart = pyb.millis()
flashMode = 0
pedalStart = pyb.millis()
stepsPerBpmUnit = 1
    
ugfx.init()
buttons.init()
pedalPin = pyb.Pin("X1", pyb.Pin.IN, pyb.Pin.PULL_UP)

ugfx.clear(ugfx.WHITE)

resetSleep()
showBpm()
showPlay()
showG()

uart = UART(3, 31250)
uart.init(31250, bits=8, parity=None, stop=1)




while True:
    if buttons.is_triggered("BTN_B"):
        updateMode(+1)
        resetSleep()
    if buttons.is_triggered("BTN_A"):
        updateMode(-1)
        resetSleep()
    if buttons.is_triggered("JOY_RIGHT") :
        nextBeat()
        resetSleep()
    if pedalGoneUp and pedalPin.value() == 0:
        pedalGoneUp = False
        pedalBounceFlag = False
        nextBeat()
        resetSleep()
    if not pedalGoneUp:
        if not pedalBounceFlag:
            if pedalPin.value() == 1: # Gone up
                pedalBounceFlag = True
                pedalStart = pyb.millis()
        else: # Started debounce
            if pedalPin.value() == 1: # Still up
                if pyb.elapsed_millis(pedalStart) > pedalInterval:
                    pedalGoneUp = True
            else: # bouncing
                pedalBounceFlag = False
    
    if buttons.is_triggered("BTN_MENU") :
        nextBpmStep()
        resetSleep()
        
    if pyb.elapsed_millis(beatStart)>=(60000/bpms[bpmp])/stepsPerBpmUnit and playOn:
        nextBeat()
        resetSleep()
    if buttons.is_triggered("JOY_CENTER"):
        playOn = not playOn
        showPlay()
        resetSleep()
    if pyb.elapsed_millis(ledStart)>100:
        pyb.LED(1).off()
        pyb.LED(2).off()
    if buttons.is_triggered("JOY_LEFT"):
        flashMode = (flashMode + 1) % 3
        if flashMode == 1:
            ledStart = pyb.millis()
            pyb.LED(2).on()
        if flashMode == 2:
            ledStart = pyb.millis()
            pyb.LED(1).on()
        resetSleep()
            
        
    if buttons.is_pressed("JOY_UP"):
        resetSleep()
        if pyb.elapsed_millis(upStart) > upInterval:
            if bpmp < numBpms-1:
                bpmp=bpmp+1
                showBpm()
            upStart = pyb.millis()
            upCount =  upCount + 1
            if upCount <2:
                upInterval = firstInterval
            else:
                upInterval = secondInterval
    else:
        upInterval = zeroInterval
        upCount = 0
        
    if buttons.is_pressed("JOY_DOWN"):
        resetSleep()
        if pyb.elapsed_millis(downStart) > downInterval:
            if bpmp > 0:
                bpmp = bpmp - 1
                showBpm()
            downStart = pyb.millis()
            downCount =  downCount + 1
            if downCount <2:
                downInterval = firstInterval
            else:
                downInterval = secondInterval
    else:
        downInterval = zeroInterval
        downCount = 0
        
    if buttons.is_pressed("BTN_A") and buttons.is_pressed("BTN_B"):
        ugfx.backlight(0)

    if pyb.elapsed_millis(sleepStart) > sleepInterval:
        ugfx.backlight(0)
    elif pyb.elapsed_millis(sleepStart) > preSleepInterval:
        ugfx.backlight(20)
        
