#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;
}