U0 SongPuppet(CTask *task, I64 passes)
{
	CDebugInfo *debug_info;
	I64 i, start, end, rip, last_rip;
	CHashFun *tmpf=NULL;

	for (i = 0; i < 250 && TaskValidate(task); i++)
	{
		if (tmpf = HashFind("Song", task->hash_table, HTT_FUN))
			break;
		Sleep(1);
	}
	if (tmpf && (debug_info = tmpf->debug_info))
	{
		start = debug_info->body[0];
		end   = debug_info->body[debug_info->max_line + 1 - debug_info->min_line];
		last_rip = 0;
		while (TRUE)
		{
			i = 0;
			while (TaskValidate(task) && (rip = TaskCaller(task, i++)))
			{
				if (start <= rip < end) {
					if (rip < last_rip && --passes <= 0)
						return;
					last_rip = rip;
				}
			}
			Sleep(1);
		}
	}
}

U0 JukeSongPuppet(CTask *juke_task, I64 passes, I64 song_num, U8 *name)
{
	Bool	found;
	CTask  *task;
	I64		i;

	if (FileExtDot(name))
		FileExtRemove(name);
	BirthWait(&juke_task->popup_task);
	TaskWait(juke_task->popup_task);
	MessagePost(juke_task->popup_task, MESSAGE_KEY_DOWN_UP, 0, SC_CURSOR_UP + SCF_CTRL);
	TaskWait(juke_task->popup_task);
	for (i = 0; i < song_num; i++)
	{
		MessagePost(juke_task->popup_task, MESSAGE_KEY_DOWN_UP, 0, SC_CURSOR_RIGHT);
		TaskWait(juke_task->popup_task);
	}

	MessagePost(juke_task->popup_task, MESSAGE_KEY_DOWN_UP, CH_SPACE, 0);
	TaskWait(juke_task->popup_task);
	Sleep(500);

	found = FALSE;
	task = Fs->next_task;
	while (task != Fs) {
		if (!StrCompare(task->task_title, name))
		{
			found = TRUE;
			break;
		}
		task = task->next_task;
	}

	if (found)
	{//Position cursor on song title during song
		TaskWait(juke_task->popup_task);
		MessagePost(juke_task->popup_task, MESSAGE_KEY_DOWN_UP, 0, SC_CURSOR_UP + SCF_CTRL);
		TaskWait(juke_task->popup_task);
		for (i = 0; i < song_num; i++)
		{
			MessagePost(juke_task->popup_task, MESSAGE_KEY_DOWN_UP, 0, SC_CURSOR_RIGHT);
			TaskWait(juke_task->popup_task);
		}
		SongPuppet(task, passes);
	}
	Kill(task);
}

public U0 JukeSongsPuppet(U8 *dirname="~/Psalmody", I64 passes=2, I64 start_song=0, I64 end_song=I64_MAX)
{//Help make music video by puppeting JukeBox task.
	I64			 i;
	CDirEntry	*tmpde, *tmpde1;
	CTask		*juke_task = User("JukeBox(0x%X);\n", dirname);
	F64			 t0;

	Cd(dirname);
	tmpde1 = FilesFind("*", FUF_RECURSE | FUF_JUST_TXT | FUF_JUST_FILES);
	for (tmpde = tmpde1, i = 0; tmpde && i < start_song; i++)
		tmpde = tmpde->next;
	if (screencast.record)
		t0 = screencast.t0_tS;
	else
		t0 = tS;
	for (i = start_song; tmpde && i < end_song; i++)
	{
		"%12.6fs %s\n", tS - t0, tmpde->full_name;
		JukeSongPuppet(juke_task, passes, i, tmpde->name);
		tmpde = tmpde->next;
	}
	DirTreeDel(tmpde1);
	Kill(juke_task);
}
