# Copyright (C) 2026 Nathan Cerisara <https://github.com/nath54/nasong>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


"""Keyboard and piano instrument definitions.

This module provides factory functions for piano-like instruments, simulating
hammer-strike harmonics and typical acoustic piano envelopes.
"""

#
### Import Modules. ###
#
import math

#
import nasong.core.all_values as lv


#
### CATEGORY: KEYBOARDS ###
#


def PianoNote(
    time: lv.Value,
    frequency: float,
    start_time: float,
    duration: float,
    amplitude: float = 0.3,
) -> lv.Value:
    """Creates a basic acoustic piano note sound.

    Synthesized using four additive sine harmonics (fundamental, 2nd, 3rd, and
    4th) shaped by an ADSR2 envelope with a quick attack and moderate release.

    Args:
        time (lv.Value): The global time value.
        frequency (float): Fundamental frequency (Hz).
        start_time (float): Activation time in seconds.
        duration (float): Length of the note in seconds.
        amplitude (float, optional): Overall volume scaling. Defaults to 0.3.

    Returns:
        lv.Value: The audio value graph for the piano note.
    """

    #
    ### Create ADSR envelope  ###
    #
    envelope: lv.Value = lv.ADSR2(
        time=time,
        note_start=start_time,
        note_duration=duration,
        attack_time=0.02,
        decay_time=0.15,
        sustain_level=0.6,
        release_time=0.3,
    )

    #
    ### Piano harmonics  ###
    #
    pi2: float = 2 * math.pi
    #
    fundamental: lv.Value = lv.Sin(
        value=time, frequency=lv.Constant(frequency * pi2), amplitude=lv.Constant(1.0)
    )
    #
    harmonic2: lv.Value = lv.Sin(
        value=time,
        frequency=lv.Constant(frequency * 2 * pi2),
        amplitude=lv.Constant(0.4),
    )
    #
    harmonic3: lv.Value = lv.Sin(
        value=time,
        frequency=lv.Constant(frequency * 3 * pi2),
        amplitude=lv.Constant(0.2),
    )
    #
    harmonic4: lv.Value = lv.Sin(
        value=time,
        frequency=lv.Constant(frequency * 4 * pi2),
        amplitude=lv.Constant(0.1),
    )

    #
    ### Pre-build the sum of harmonics  ###
    #
    harmonic_sum: lv.Value = lv.Sum([fundamental, harmonic2, harmonic3, harmonic4])

    #
    ### Final = Amplitude * Envelope * Signal ###
    #
    return lv.Product(lv.c(amplitude), envelope, harmonic_sum)


#
def PianoNote2(
    time: lv.Value,
    frequency: float,
    start_time: float,
    duration: float = 2.0,
    amplitude: float = 0.3,
) -> lv.Value:
    """Creates an alternate piano note with exponential decay.

    Uses five harmonics and an ExponentialADSR envelope to simulate a more
    dynamic and natural decaying piano sound.

    Args:
        time (lv.Value): The global time value.
        frequency (float): Fundamental frequency (Hz).
        start_time (float): Activation time in seconds.
        duration (float, optional): Length of the note in seconds. Defaults to 2.0.
        amplitude (float, optional): Overall volume scaling. Defaults to 0.3.

    Returns:
        lv.Value: The audio value graph for the alternate piano note.
    """

    #
    ### Envelope: Approximates the 4-stage original  ###
    #
    amp_env: lv.Value = lv.ExponentialADSR(
        time,
        note_start=start_time,
        note_duration=duration,
        attack_time=0.01,
        decay_time=0.09,
        sustain_level=0.7,
        release_time=0.5,
        attack_curve=1.0,
        decay_curve=1.0,
    )

    #
    ### Harmonics  ###
    #
    relative_time: lv.Value = lv.BasicScaling(time, lv.c(1), lv.c(-start_time))
    pi2: float = 2 * math.pi
    #
    h1: lv.Value = lv.Sin(relative_time, lv.c(frequency * 1 * pi2), lv.c(1.0))
    h2: lv.Value = lv.Sin(relative_time, lv.c(frequency * 2 * pi2), lv.c(0.5))
    h3: lv.Value = lv.Sin(relative_time, lv.c(frequency * 3 * pi2), lv.c(0.25))
    h4: lv.Value = lv.Sin(relative_time, lv.c(frequency * 4 * pi2), lv.c(0.15))
    h5: lv.Value = lv.Sin(relative_time, lv.c(frequency * 5 * pi2), lv.c(0.1))

    #
    signal: lv.Value = lv.Sum(h1, h2, h3, h4, h5)

    #
    ### Final = amplitude * AmpEnv * Signal ###
    #
    return lv.Product(lv.c(amplitude), amp_env, signal)
