#help_index "Sound"
public U0 SoundTaskEndCB()
{//Will turn-off sound when a task gets killed.
    Sound;
    Exit;
}

#help_index "Sound/Math;Math"
public F64 Saw(F64 t, F64 period)
{//Sawtooth. 0.0 - 1.0 think "(Sin+1)/2"
    if (period)
    {
        if (t >= 0.0)
            return t % period / period;
        else
            return 1.0 + t % period / period;
    }
    else
        return 0.0;
}

public F64 FullSaw(F64 t, F64 period)
{//Plus&Minus Sawtooth. 1.0 - -1.0 think "Sin"
    if (period)
    {
        if (t >= 0.0)
            return 2.0 * (t % period / period) - 1.0;
        else
            return 2.0 * (t % period / period) + 1.0;
    }
    else
        return 0.0;
}

public F64 Caw(F64 t, F64 period)
{//Cawtooth. 1.0 - 0.0 think "(Cos+1)/2"
    if (period)
    {
        if (t >= 0.0)
            return 1.0 - t % period / period;
        else
            return -(t % period) / period;
    }
    else
        return 1.0;
}

public F64 FullCaw(F64 t, F64 period)
{//Plus&Minus Cawtooth. 1.0 - -1.0 think "Cos"
    if (period)
    {
        if (t >= 0.0)
            return -2.0 * (t % period / period) + 1.0;
        else
            return -2.0 * (t % period / period) - 1.0;
    }
    else
        return 1.0;
}

public F64 Tri(F64 t, F64 period)
{//Triangle waveform. 0.0 - 1.0 - 0.0
    if (period)
    {
        t = 2.0 * (Abs(t) % period) / period;
        if (t <= 1.0)
            return t;
        else
            return 2.0 - t;
    }
    else
        return 0.0;
}

public F64 FullTri(F64 t, F64 period)
{//Plus&Minus Triangle waveform. 0.0 - 1.0 - 0.0 - -1.0 -0.0
    if (period)
    {
        t = 4.0 * (t % period) / period;
        if (t <= -1.0)
        {
            if (t <= -3.0)
                return t + 4.0;
            else
                return -2.0 - t;
        }
        else
        {
            if (t <= 1.0)
                return t;
            else if (t <= 3.0)
                return 2.0 - t;
            else
                return t -4.0;
        }
    }
    else
        return 0.0;
}

#help_index "Sound/Music"

public class CMusicGlobals
{
    U8      *cur_song;
    CTask   *cur_song_task;
    I64      octave;
    F64      note_len;
    U8       note_map[7];
    Bool     mute;
    I64      meter_top, meter_bottom;
    F64      tempo, stacatto_factor;

    //If you wish to sync with a
    //note in a Play() string.  0 is the start
    I64      play_note_num;

    F64      tM_correction, last_Beat, last_tM;

} music = {NULL, NULL, 4, 1.0, {0, 2, 3, 5, 7, 8, 10}, FALSE, 4, 4, 2.5, 0.9, 0, 0, 0, 0};

#help_index "Sound/Music;Time/Seconds"
public F64 tM()
{//Time in seconds synced to music subsystem.
    return (counts.jiffies + music.tM_correction) / JIFFY_FREQ;
}

public F64 Beat()
{//Time in music beats.
    F64 res, cur_tM;

    PUSHFD
    CLI
    if (mp_count > 1)
        while (LBts(&sys_semas[SEMA_TMBEAT], 0))
            PAUSE
    cur_tM = tM;
    res = music.last_Beat;
    if (music.tempo)
        res += (cur_tM - music.last_tM) * music.tempo;
    music.last_tM = cur_tM;
    music.last_Beat = res;
    LBtr(&sys_semas[SEMA_TMBEAT], 0);
    POPFD

    return res;
}

#help_index "Sound/Music"
U8 *MusicSetOctave(U8 *st)
{
    I64 ch;

    ch = *st++;
    while ('0' <= ch <= '9')
    {
        music.octave = ch - '0';
        ch = *st++;
    }

    return --st;
}

U8 *MusicSetMeter(U8 *st)
{
    I64 ch;

    ch = *st++;
    while (ch == 'M')
    {
        ch = *st++;
        if ('0' <= ch <= '9')
        {
            music.meter_top = ch - '0';
            ch = *st++;
        }
        if (ch == '/')
            ch = *st++;
        if ('0' <= ch <= '9')
        {
            music.meter_bottom = ch - '0';
            ch = *st++;
        }
    }

    return --st;
}

U8 *MusicSetNoteLen(U8 *st)
{
    Bool cont=TRUE;

    do
    {
        switch (*st++)
        {
            case 'w': music.note_len = 4.0;                         break;
            case 'h': music.note_len = 2.0;                         break;
            case 'q': music.note_len = 1.0;                         break;
            case 'e': music.note_len = 0.5;                         break;
            case 's': music.note_len = 0.25;                        break;
            case 't': music.note_len = 2.0 * music.note_len / 3.0;  break;
            case '.': music.note_len = 1.5 * music.note_len;        break;

            default:
                st--;
                cont = FALSE;
        }
    }
    while (cont);

    return st;
}

public I8 Note2Ona(I64 note, I64 octave=4)
{//Note to ona. Mid C is ona=51, note=3 and octave=4.
    if (note < 3)
        return (octave + 1) * 12 + note;
    else
        return octave * 12 + note;
}

public I8 Ona2Note(I8 ona)
{//Ona to note in octave. Mid C is ona=51, note=3 and octave=4.
    return ona % 12;
}

public I8 Ona2Octave(I8 ona)
{//Ona to octave. Mid C is ona=51, note=3 and octave=4.
    I64 note = ona % 12, octave = ona / 12;

    if (note < 3)
        return octave - 1;
    else
        return octave;
}

public U0 Play(U8 *st, U8 *words=NULL)
{/* Notes are entered with a capital letter.

Octaves are entered with a digit and
stay set until changed.  Mid C is octave 4.

Durations are entered with
'w' whole note
'h' half note
'q' quarter note
'e' eighth note
't' sets to 2/3rds the current duration
'.' sets to 1.5 times the current duration
durations stay set until changed.

'(' tie, placed before the note to be extended

music.meter_top,music.meter_bottom is set with
"M3/4"
"M4/4"
etc.

Sharp and flat are done with '#' or 'b'.

The var music.stacatto_factor can
be set to a range from 0.0 to 1.0.

The var music.tempo is quarter-notes
per second.  It defaults to
2.5 and gets faster when bigger.
*/
    U8  *word, *last_st;
    I64  note, octave, i = 0, ona, timeout_val, timeout_val2;
    Bool tie;
    F64  d, on_jiffies, off_jiffies;

    music.play_note_num = 0;
    while (*st)
    {
        timeout_val = counts.jiffies;
        tie = FALSE;

        do
        {
            last_st = st;
            if (*st == '(')
            {
                tie=TRUE;
                st++;
            }
            else
            {
                st = MusicSetMeter(st);
                st = MusicSetOctave(st);
                st = MusicSetNoteLen(st);
            }
        }
        while (st != last_st);

        if (!*st)
            break;
        note = *st++ - 'A';
        if (note < 7)
        {
            note = music.note_map[note];
            octave = music.octave;
            if (*st == 'b')
            {
                note--;
                if (note == 2)
                    octave--;
                st++;
            }
            else if (*st == '#')
            {
                note++;
                if (note == 3)
                    octave++;
                st++;
            }
            ona = Note2Ona(note, octave);
        }
        else
            ona = 0;
        if (words && (word = ListSub(i++, words)) && StrCompare(word, " "))
            "%s", word;

        d = JIFFY_FREQ * music.note_len / music.tempo;
        on_jiffies  = d * music.stacatto_factor;
        off_jiffies = d * (1.0 - music.stacatto_factor);

        timeout_val += on_jiffies;
        timeout_val2 = timeout_val + off_jiffies;

        if (!music.mute)
            Sound(ona);
        SleepUntil(timeout_val);
        music.tM_correction += on_jiffies - ToI64(on_jiffies);

        if (!music.mute && !tie)
            Sound;
        SleepUntil(timeout_val2);
        music.tM_correction += off_jiffies - ToI64(off_jiffies);

        music.play_note_num++;
    }
}

U0 MusicSettingsReset()
{
    music.play_note_num     = 0;
    music.stacatto_factor   = 0.9;
    music.tempo             = 2.5;
    music.octave            = 4;
    music.note_len          = 1.0;
    music.meter_top         = 4;
    music.meter_bottom      = 4;
    SoundReset;
    PUSHFD
    CLI
    if (mp_count > 1)
        while (LBts(&sys_semas[SEMA_TMBEAT], 0))
            PAUSE
    music.last_tM = tM;
    music.last_Beat = 0.0;
    LBtr(&sys_semas[SEMA_TMBEAT], 0);
    POPFD
}

MusicSettingsReset;

U0 CurSongTask()
{
    Fs->task_end_cb = &SoundTaskEndCB;
    while (TRUE)
        Play(music.cur_song);
}

#help_index "Sound"

#define SE_NOISE        0
#define SE_SWEEP        1

class CSoundEffectFrame
{
    I32     type;
    I8      ona1, ona2;
    F64     duration;
};

U0 SoundEffectEndTaskCB()
{
    Free(FramePtr("CSoundEffectFrame"));
    music.mute--;
    SoundTaskEndCB;
}

U0 SoundEffectTask(CSoundEffectFrame *ns)
{
    I64 i, ona;
    F64 t0 = tS, t, timeout = t0 + ns->duration;

    FramePtrAdd("CSoundEffectFrame", ns);
    Fs->task_end_cb = &SoundEffectEndTaskCB;
    switch (ns->type)
    {
        case SE_NOISE:
            i = MaxI64(ns->ona2 - ns->ona1, 1);
            while (tS < timeout)
            {
                ona = RandU16 % i + ns->ona1;
                Sound(ona);
                t = Clamp(3000.0 / Ona2Freq(ona), 1.0, 50.0);
                if (t + tS > timeout)
                    t = timeout - tS;
                Sleep(t);
            }
            break;

        case SE_SWEEP:
            while (tS<timeout)
            {
                t = (tS - t0) / ns->duration;
                ona = (1.0 - t) * ns->ona1 + t * ns->ona2;
                Sound(ona);
                t = Clamp(3000.0 / Ona2Freq(ona), 1.0, 50.0);
                if (t + tS > timeout)
                    t = timeout  -tS;
                Sleep(t);
            }
            break;
    }
}

public CTask *Noise(I64 mS, F64 min_ona, F64 max_ona)
{//Make white noise for given number of mS.
    CSoundEffectFrame *ns;

    if (mS > 0)
    {
        ns = MAlloc(sizeof(CSoundEffectFrame));
        ns->type = SE_NOISE;
        ns->duration = mS / 1000.0;
        ns->ona1 = min_ona;
        ns->ona2 = max_ona;
        music.mute++;
        return Spawn(&SoundEffectTask, ns, "Noise",, Fs);
    }
    else
        return NULL;
}

public CTask *Sweep(I64 mS, F64 ona1, F64 ona2)
{//Sweep through freq range in given number of mS.
    CSoundEffectFrame *ns;

    if (mS > 0)
    {
        ns = MAlloc(sizeof(CSoundEffectFrame));
        ns->type = SE_SWEEP;
        ns->duration = mS / 1000.0;
        ns->ona1 = ona1;
        ns->ona2 = ona2;
        music.mute++;
        return Spawn(&SoundEffectTask, ns, "Noise",, Fs);
    }
    else
        return NULL;
}