# 2_modulation/code_portamento.py
# part of todbot circuitpython synthio tutorial
# 10 Feb 2025 - @todbot / Tod Kurt
#
import time, random
import synthio
import ulab.numpy as np
from synth_setup import synth, knobA

class Glider:
    """Attach a Glider to note.bend to implement portamento"""
    def __init__(self, glide_time, midi_note):
        self.pos = synthio.LFO(once=True, rate=1/glide_time,
                               waveform=np.array((0,32767), dtype=np.int16))
        self.lerp = synthio.Math(synthio.MathOperation.CONSTRAINED_LERP,
                                 0, 0, self.pos)
        self.midi_note = midi_note

    def update(self, new_midi_note):
        """Update the glide destination based on new midi note"""
        self.lerp.a = self.lerp.value  # current value is now start value
        self.lerp.b = self.lerp.a + self.bend_amount(self.midi_note, new_midi_note)
        self.pos.retrigger()  # restart the lerp
        self.midi_note = new_midi_note

    def bend_amount(self, old_midi_note, new_midi_note):
        """Calculate how much note.bend has to happen between two notes"""
        return (new_midi_note - old_midi_note)  * (1/12)

    @property
    def glide_time(self):
        return 1/self.pos.rate
    @glide_time.setter
    def glide_time(self, glide_time):
        self.pos.rate = 1/glide_time

glide_time = 0.25
midi_notes = [48, 36, 24, 36]
new_midi_note = midi_notes[0]

# create a portamento glider and attach it to a note
glider = Glider(glide_time, new_midi_note)
note = synthio.Note(synthio.midi_to_hz(new_midi_note), bend=glider.lerp)
synth.press(note)   # start the note sounding

i=0
while True:
    glider.glide_time = 0.5 * (knobA.value/65535)
    new_midi_note = midi_notes[i]  # new note to glide to
    note.frequency = synthio.midi_to_hz(new_midi_note)
    glider.update(new_midi_note)  # glide up to new note
    i = (i+1) % len(midi_notes)
    print("new: %d old: %d glide_time: %.2f" % (new_midi_note, glider.midi_note, glider.glide_time))
    time.sleep(0.5)
