# beat_player.py

# Raspberry Pi Pico - Rhythmic Step Sequencer demo

# This assumes a tiny 9G servo has been wired up to the Pico as follows:
#   Pico pin 40 (VBUS)  -> servo red   (+5V)
#   Pico pin 38 (GND)   -> servo brown (GND)
#   Pico pin 1  (GP0)   -> servo orange (SIG)

#--------------------------------------------------------------------------------
# Import standard modules.
import time

# Load the CircuitPython hardware definition module for pin definitions.
import board

# Import course modules.  These files should be copied to the top-level
# directory of the CIRCUITPY filesystem on the Pico.
import servo
import sequencer
import remote

#---------------------------------------------------------------
# Define motion control class to execute sequencer event callbacks on a hobby servo.
class BeatServo:
    def __init__(self, servo):
        """Create musical beats on a hobby servo."""
        self.servo = servo
        self.intensity = 45
        self.state = False

    def note_event(self, char):
        """Callback to receive sequencer events encoded as single characters."""

        # ignore whitespace or period, these will be treated as a rest
        if not (char.isspace() or char == '.'):
            # use the character value to set the movement magnitude
            if char in '+abcdefg':
                self.intensity = 15
            else:
                self.intensity = 60

            # toggle the servo state
            if self.state:
                self.servo.write(90 + self.intensity)
                self.state = False
            else:
                self.servo.write(90 - self.intensity)
                self.state = True
            
    def poll(self, elapsed):
        """This object doesn't yet need a polling function, for now it only updates on
        callback events.
        """
        pass
            
#--------------------------------------------------------------------------------
# Create an object to represent a servo on the given hardware pin.
servo = servo.Servo(board.GP0)

# Create beat motion control connected to the servo.
motion = BeatServo(servo)

# Create a sequencer and connect it to the servo motion control.
sequencer = sequencer.Sequencer()
sequencer.set_note_handler(motion.note_event)

# Set a test pattern to loop.
sequencer.set_pattern("#   #   #   +   +   + + +   #+++")   

# Set up communication interface and callbacks.
remote  = remote.RemoteSerial()

def default_handler(msgtype, *args):
    print(f"Warning: received unknown message {msgtype} {args}")

remote.add_default_handler(default_handler)    
remote.add_handler('tempo', sequencer.set_tempo)
remote.add_handler('pattern', sequencer.set_pattern)

#---------------------------------------------------------------
# Main event loop to run each non-preemptive thread.

last_clock = time.monotonic_ns()

while True:
    # read the current nanosecond clock
    now = time.monotonic_ns()
    elapsed = now - last_clock
    last_clock = now

    # poll each thread
    remote.poll(elapsed)    
    sequencer.poll(elapsed)    
    
