##
# @module    sounds
# @brief     sound functions and classes (previously 'UI')
# @version   1.0
#

import ft8xx as ft
import vts

def singleton(cls):
    instance = None
    def getinstance(*args, **kwargs):
        nonlocal instance
        if instance is None:
            instance = cls(*args, **kwargs)
        # else:
        #     print("Only one speaker object allowed. Returned original Instance")
        return instance
    return getinstance

@singleton
class Speaker:

    snd_click = 0
    snd_error = 1
    snd_1s_beep = 2
    snd_2s_beep = 3
    snd_generic_beep = 4
    snd_target_speed_beep = 3
    sounds = [
        # click
        [(0, 88)],
        # error
        [(100,  0x01,  52, 220),
         (300,  0x01,  40, 220)],
        # 1 second beep
        [(1000, 4, 88)],
        # 2 second beep
        [(2000, 4, 88)],
        # generic beep
        [(500, 4, 88)],
    ]

    def __init__(self, volume=128):
        self.sound_list = None
        self.sound_timer = None
        self.set_volume(volume)

    def set_volume(self, volume):
        self.volume = volume
        ft.wr8(ft.REG_VOL_SOUND, self.volume)

    def mute(self):
        self.set_volume(0)

    def play_sound(self, index):
        self.sound_start(type(self).sounds[index])

    def add_sound(self, sound_name, sound):
        if type(sound) == list and len(sound):
            for user_sound in sound:
                if type(user_sound) == tuple and len(user_sound) > 1:
                    pass
                else:
                    raise Exception("Sound format incorrect")
        else:
            raise Exception("Sound format incorrect")
            
        setattr(type(self), sound_name, len(type(self).sounds))
        type(self).sounds.append(sound)

    # Play sound from a sound list. A sound list is a list of tuples consisting
    # of:
    #   - note duration in milliseconds (0 to let last note ring out)
    #   - note instrument
    #   - note pitch (optional)
    #   - note volume (optional)
    # Volume is 0-255, relative to the "master" volume.
    def sound_start(self, sl):
        # Copy sound list
        self.sound_list = sl[::-1]
        self.sound_cb()

    # Timer callback for sound
    def sound_cb(self):
        try:
            self.sound_timer.destroy()
        except (TypeError, AttributeError):
            # Catch: timer already destroyed or timer variable is None
            pass
        if not len(self.sound_list):
            # No notes left but timer was set - silence current note and exit.
            # NOTE: Writing 0 to REG_SOUND then 1 to REG_PLAY to stop playing
            # sound, as the programmers guide suggests, sometimes makes the next
            # note played sound double. So we just cut the volume instead.
            ft.wr8(ft.REG_VOL_SOUND, 0)
            return
        note = self.sound_list.pop()
        sound = note[1]
        if len(note) >= 3:
            sound |= note[2] << 8
        if len(note) >= 4:
            vol = (note[3] * self.volume) >> 8
        else:
            vol = self.volume
        ft.wr8(ft.REG_VOL_SOUND, vol)
        ft.wr16(ft.REG_SOUND, sound)
        ft.wr8(ft.REG_PLAY, 1)
        if note[0] != 0:
            self.sound_timer = vts.Timer(note[0], False)
            self.sound_timer.set_callback(self.sound_cb)

    def sound_cb_pending(self):
        return False if self.sound_timer is None or not self.sound_timer.pending() else True