#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

$LK,"music.meter_top",A="MN:CMusicGlobals"$,$LK,"music.meter_bottom",A="MN:CMusicGlobals"$ 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;
}
