# SPDX-FileCopyrightText: 2017, 2018 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`adafruit_rtttl`
====================================================

Play notes to a digialio pin using ring tone text transfer language (rtttl).

* Author(s): Scott Shawcroft
"""

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RTTTL"

import sys
import time
import pwmio

AUDIOIO_AVAILABLE = False
try:
    import audioio
    from adafruit_waveform import sine

    AUDIOIO_AVAILABLE = True
    try:
        import audiocore
    except ImportError:
        audiocore = audioio
except ImportError:
    pass

try:
    from typing import Optional, Union, Tuple, List
    from audioio import AudioOut
except ImportError:
    pass

PIANO = {
    "4c": 261.626,
    "4c#": 277.183,
    "4d": 293.665,
    "4d#": 311.127,
    "4e": 329.628,
    "4f": 349.228,
    "4f#": 369.994,
    "4g": 391.995,
    "4g#": 415.305,
    "4a": 440,
    "4a#": 466.164,
    "4b": 493.883,
    "5c": 523.251,
    "5c#": 554.365,
    "5d": 587.330,
    "5d#": 622.254,
    "5e": 659.255,
    "5f": 698.456,
    "5f#": 739.989,
    "5g": 783.991,
    "5g#": 830.609,
    "5a": 880,
    "5a#": 932.328,
    "5b": 987.767,
    "6c": 1046.50,
    "6c#": 1108.73,
    "6d": 1174.66,
    "6d#": 1244.51,
    "6e": 1318.51,
    "6f": 1396.91,
    "6f#": 1479.98,
    "6g": 1567.98,
    "6g#": 1661.22,
    "6a": 1760,
    "6a#": 1864.66,
    "6b": 1975.53,
    "7c": 2093,
    "7c#": 2217.46,
}


def _parse_note(note: str, duration: int = 2, octave: int = 6) -> Tuple[str, float]:
    note = note.strip()
    piano_note = None
    note_duration = duration
    if note[0].isdigit() and note[1].isdigit():
        note_duration = int(note[:2])
        piano_note = note[2]
    elif note[0].isdigit():
        note_duration = int(note[0])
        piano_note = note[1]
    else:
        piano_note = note[0]
    if "." in note:
        note_duration *= 1.5
    if "#" in note:
        piano_note += "#"
    note_octave = str(octave)
    if note[-1].isdigit():
        note_octave = note[-1]
    piano_note = note_octave + piano_note
    return piano_note, note_duration


def _get_wave(tune: str, octave: int) -> Tuple[List[int], float]:
    """Returns the proper waveform to play the song along with the minimum
    frequency in the song.
    """
    min_freq = 13000

    for note in tune.split(","):
        piano_note, _ = _parse_note(note, octave=octave)
        if piano_note[-1] != "p" and PIANO[piano_note] < min_freq:
            min_freq = PIANO[piano_note]
    return sine.sine_wave(16000, min_freq), min_freq


# pylint: disable-msg=too-many-arguments
def _play_to_pin(
    tune: str,
    base_tone: Union[pwmio.PWMOut, AudioOut],
    min_freq: float,
    duration: int,
    octave: int,
    tempo: int,
) -> None:
    """Using the prepared input send the notes to the pin"""
    pwm = isinstance(base_tone, pwmio.PWMOut)
    for note in tune.split(","):
        piano_note, note_duration = _parse_note(note, duration, octave)
        if piano_note in PIANO:
            if pwm:
                base_tone.frequency = int(PIANO[piano_note])
                base_tone.duty_cycle = 2 ** 15
            else:
                # AudioOut interface changed in CP 3.x
                if sys.implementation.version[0] >= 3:
                    pitch = int(PIANO[piano_note])
                    sine_wave = sine.sine_wave(16000, pitch)
                    sine_wave_sample = audiocore.RawSample(sine_wave)
                    base_tone.play(sine_wave_sample, loop=True)
                else:
                    base_tone.frequency = int(16000 * (PIANO[piano_note] / min_freq))
                    base_tone.play(loop=True)

        time.sleep(4 / note_duration * 60 / tempo)
        if pwm:
            base_tone.duty_cycle = 0
        else:
            base_tone.stop()
        time.sleep(0.02)


# pylint: disable-msg=too-many-arguments
def play(
    pin,
    rtttl: str,
    octave: int = Optional[None],
    duration: Optional[int] = None,
    tempo: Optional[int] = None,
) -> None:
    """Play notes to a digialio pin using ring tone text transfer language (rtttl).
    :param ~digitalio.DigitalInOut pin: the speaker pin
    :param str rtttl: string containing rtttl
    :param int octave: represents octave number (default 6 starts at middle c)
    :param int duration: length of notes (default 4 quarter note)
    :param int tempo: how fast (default 63 beats per minute)
    """
    _, defaults, tune = rtttl.lower().split(":")
    for default in defaults.split(","):
        if default[0] == "d" and not duration:
            duration = int(default[2:])
        elif default[0] == "o" and not octave:
            octave = default[2:]
        elif default[0] == "b" and not tempo:
            tempo = int(default[2:])
    if not octave:
        octave = 6
    if not duration:
        duration = 4
    if not tempo:
        tempo = 63

    base_tone = None
    min_freq = 440
    if AUDIOIO_AVAILABLE:
        wave, min_freq = _get_wave(tune, octave)
        try:
            # AudioOut interface changed in CP 3.x; a waveform if now pass
            # directly to .play(), generated for each note in _play_to_pin()
            if sys.implementation.version[0] >= 3:
                base_tone = audioio.AudioOut(pin)
            else:
                base_tone = audioio.AudioOut(pin, wave)
        except ValueError:
            # No DAC on the pin so use PWM.
            pass

    # Fall back to PWM
    if not base_tone:
        base_tone = pwmio.PWMOut(pin, duty_cycle=0, variable_frequency=True)

    _play_to_pin(tune, base_tone, min_freq, duration, octave, tempo)

    base_tone.deinit()
