/*
 * Copyright 2020-2026 Yuntu Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * @file dac_music.c
 * @brief Simple music player driving the ACMP internal 8-bit DAC (PTC8/DAC0_OUT).
 */

#include "dac_music.h"
#include "clock.h" /* pulls in device registers (ACMP0, DWT, CoreDebug, ACMP_DAC_*) and CLOCK_SYS_GetFreq/CORE_CLK */

/*******************************************************************************
 * Definitions
 ******************************************************************************/
/*! @brief Audio sample rate of the direct-digital-synthesis engine (Hz). */
#define DAC_MUSIC_SAMPLE_RATE_HZ (20000U)
/*! @brief DAC mid-scale code, output stays here while silent. */
#define DAC_MUSIC_DC_LEVEL (128U)
/*! @brief Silence inserted at the end of every note so repeated notes are heard apart (ms). */
#define DAC_MUSIC_NOTE_GAP_MS (30U)
/*! @brief Fallback core frequency if the clock manager cannot report one. */
#define DAC_MUSIC_DEFAULT_CORE_HZ (24000000U)

/*! @brief One melody entry: a note (or NOTE_REST) and how long its slot lasts. */
typedef struct
{
    uint16_t freqHz; /*!< Note frequency, NOTE_REST for a pause */
    uint16_t durMs;  /*!< Slot length in milliseconds */
} music_note_t;

/* Note slot lengths */
#define Q (400U) /*!< Quarter note */
#define H (800U) /*!< Half note */

/*******************************************************************************
 * Variables
 ******************************************************************************/
static uint32_t s_coreClockHz = DAC_MUSIC_DEFAULT_CORE_HZ; /*!< Cached core clock */
static uint32_t s_cyclesPerSample = 0U;                    /*!< Core cycles between two audio samples */
static uint32_t s_dacRegBase = 0U;                         /*!< DAC register with EN/OUTEN set and VAL field cleared */

/*!
 * @brief Full-scale 8-bit sine table, 256 entries, centred on 128.
 *        value = round(128 + 127 * sin(2*pi*i/256))
 */
static const uint8_t s_sineTable[256] = {
    128, 131, 134, 137, 140, 144, 147, 150, 153, 156, 159, 162, 165, 168, 171, 174,
    177, 179, 182, 185, 188, 191, 193, 196, 199, 201, 204, 206, 209, 211, 213, 216,
    218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 239, 240, 241, 243, 244,
    245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254, 254, 255, 255, 255,
    255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251, 250, 250, 249, 248, 246,
    245, 244, 243, 241, 240, 239, 237, 235, 234, 232, 230, 228, 226, 224, 222, 220,
    218, 216, 213, 211, 209, 206, 204, 201, 199, 196, 193, 191, 188, 185, 182, 179,
    177, 174, 171, 168, 165, 162, 159, 156, 153, 150, 147, 144, 140, 137, 134, 131,
    128, 125, 122, 119, 116, 112, 109, 106, 103, 100, 97, 94, 91, 88, 85, 82,
    79, 77, 74, 71, 68, 65, 63, 60, 57, 55, 52, 50, 47, 45, 43, 40,
    38, 36, 34, 32, 30, 28, 26, 24, 22, 21, 19, 17, 16, 15, 13, 12,
    11, 10, 8, 7, 6, 6, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1,
    1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 6, 6, 7, 8, 10,
    11, 12, 13, 15, 16, 17, 19, 21, 22, 24, 26, 28, 30, 32, 34, 36,
    38, 40, 43, 45, 47, 50, 52, 55, 57, 60, 63, 65, 68, 71, 74, 77,
    79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 116, 119, 122, 125,
};

/*! @brief "Twinkle Twinkle Little Star" in C major. */
static const music_note_t s_demoMelody[] = {
    {NOTE_C4, Q}, {NOTE_C4, Q}, {NOTE_G4, Q}, {NOTE_G4, Q}, {NOTE_A4, Q}, {NOTE_A4, Q}, {NOTE_G4, H},
    {NOTE_F4, Q}, {NOTE_F4, Q}, {NOTE_E4, Q}, {NOTE_E4, Q}, {NOTE_D4, Q}, {NOTE_D4, Q}, {NOTE_C4, H},
    {NOTE_G4, Q}, {NOTE_G4, Q}, {NOTE_F4, Q}, {NOTE_F4, Q}, {NOTE_E4, Q}, {NOTE_E4, Q}, {NOTE_D4, H},
    {NOTE_G4, Q}, {NOTE_G4, Q}, {NOTE_F4, Q}, {NOTE_F4, Q}, {NOTE_E4, Q}, {NOTE_E4, Q}, {NOTE_D4, H},
    {NOTE_C4, Q}, {NOTE_C4, Q}, {NOTE_G4, Q}, {NOTE_G4, Q}, {NOTE_A4, Q}, {NOTE_A4, Q}, {NOTE_G4, H},
    {NOTE_F4, Q}, {NOTE_F4, Q}, {NOTE_E4, Q}, {NOTE_E4, Q}, {NOTE_D4, Q}, {NOTE_D4, Q}, {NOTE_C4, H},
};

/*******************************************************************************
 * Private code
 ******************************************************************************/

/*! @brief Enable the DWT cycle counter (free running, no debugger needed). */
static void dwt_CycleCounterInit(void)
{
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0U;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

/*! @brief Busy-wait until the cycle counter reaches the absolute time-stamp (wrap safe). */
static inline void dwt_WaitUntil(uint32_t stamp)
{
    while ((int32_t)(DWT->CYCCNT - stamp) < 0)
    {
        /* spin */
    }
}

/*! @brief Write a code to the DAC value field, keeping EN/OUTEN untouched. */
static inline void dac_SetCode(uint8_t code)
{
    ACMP0->DAC = s_dacRegBase | ACMP_DAC_VAL(code);
}

/*! @brief Millisecond delay built on the cycle counter (per-ms steps avoid overflow). */
static void dac_DelayMs(uint32_t ms)
{
    uint32_t cyclesPerMs = s_coreClockHz / 1000U;
    uint32_t stamp = DWT->CYCCNT;

    while (ms-- > 0U)
    {
        stamp += cyclesPerMs;
        dwt_WaitUntil(stamp);
    }
}

/*******************************************************************************
 * Public code
 ******************************************************************************/

void DAC_MUSIC_Init(void)
{
    uint32_t coreFreq = 0U;

    if ((CLOCK_SYS_GetFreq(CORE_CLK, &coreFreq) == STATUS_SUCCESS) && (coreFreq != 0U))
    {
        s_coreClockHz = coreFreq;
    }
    s_cyclesPerSample = s_coreClockHz / DAC_MUSIC_SAMPLE_RATE_HZ;

    dwt_CycleCounterInit();

    /* Enable the ACMP DAC and route it to the DAC0_OUT pin (PTC8); preset mid-scale. */
    uint32_t reg = ACMP0->DAC;
    reg &= ~ACMP_DAC_VAL_MASK;
    reg |= ACMP_DAC_EN(1U) | ACMP_DAC_OUTEN(1U) | ACMP_DAC_VAL(DAC_MUSIC_DC_LEVEL);
    ACMP0->DAC = reg;

    s_dacRegBase = reg & ~ACMP_DAC_VAL_MASK;
}

void DAC_MUSIC_PlayTone(uint32_t freqHz, uint32_t durationMs)
{
    if (freqHz == 0U)
    {
        DAC_MUSIC_Rest(durationMs);
        return;
    }

    /* Direct digital synthesis: a 32-bit phase accumulator indexes the sine
     * table with its top 8 bits; phaseInc advances one note period per second. */
    uint32_t phase = 0U;
    uint32_t phaseInc = (uint32_t)(((uint64_t)freqHz << 32) / DAC_MUSIC_SAMPLE_RATE_HZ);
    uint32_t totalSamples = (uint32_t)(((uint64_t)DAC_MUSIC_SAMPLE_RATE_HZ * durationMs) / 1000U);
    uint32_t stamp = DWT->CYCCNT;

    for (uint32_t i = 0U; i < totalSamples; i++)
    {
        dac_SetCode(s_sineTable[phase >> 24]);
        phase += phaseInc;
        stamp += s_cyclesPerSample;
        dwt_WaitUntil(stamp);
    }

    dac_SetCode(DAC_MUSIC_DC_LEVEL);
}

void DAC_MUSIC_Rest(uint32_t durationMs)
{
    dac_SetCode(DAC_MUSIC_DC_LEVEL);
    dac_DelayMs(durationMs);
}

void DAC_MUSIC_PlayDemo(void)
{
    uint32_t count = sizeof(s_demoMelody) / sizeof(s_demoMelody[0]);

    for (uint32_t i = 0U; i < count; i++)
    {
        uint32_t dur = s_demoMelody[i].durMs;

        if (dur > DAC_MUSIC_NOTE_GAP_MS)
        {
            DAC_MUSIC_PlayTone(s_demoMelody[i].freqHz, dur - DAC_MUSIC_NOTE_GAP_MS);
            DAC_MUSIC_Rest(DAC_MUSIC_NOTE_GAP_MS);
        }
        else
        {
            DAC_MUSIC_PlayTone(s_demoMelody[i].freqHz, dur);
        }
    }
}

/*******************************************************************************
 * EOF
 ******************************************************************************/
