void CKaraokeRotation::_getEnqueuePlaylistPosition(stSongListEntry *newSongListEntry,
												  stSingerEntry *newSinger,
												  unsigned long &entryIndex,
												  stSongListEntry **p,
												  stSongListEntry **c,
												  unsigned long &newSingerTargetRotation,
												  bool addToSpecifiedPosition,
												  bool debugFileEntry)
{
	char chDebugMsg[DEBUG_MESSAGE_SIZE];

	stSongListEntry *prevSongListEntry = NULL;
	stSongListEntry *currSongListEntry = m_songListHead;

	if (newSongListEntry->songEntry.standby==TRUE)
	{
		// Here is the standby add. This entry unconditionally goes to the end of the list.
		while (currSongListEntry)
		{
			prevSongListEntry = currSongListEntry;
			currSongListEntry = currSongListEntry->next;
			entryIndex++;
		}
		newSingerTargetRotation = m_numRotationsQueued+1;
	}

	else if (addToSpecifiedPosition)
	{
		// Here is the dumb add. Just stick it where I told you to.
		sprintf(chDebugMsg, "ENTER: Unprioritized: Song '%s' has been entered at position %u, no rotation search applied.\n",
						newSongListEntry->songEntry.songName,
						entryIndex+1
				);
		WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));

		// If this is a singer in rotation, set the target rotation to the current rotation value.
		// If this is the placeholder singer, set target rotation to OR.
		if (GetPlaceholderSinger() == newSinger)
		{
			newSingerTargetRotation = OUT_OF_ROTATION;
		}
		else
		{
			newSingerTargetRotation = m_currentRotationValue;
		}

		for (unsigned long i=0; i<entryIndex && currSongListEntry!=NULL; i++)
		{
			prevSongListEntry=currSongListEntry;
			currSongListEntry=currSongListEntry->next;
		}
	}

	else
	{
		bool isNewSinger = (newSinger->numberOfSongsSung == 0) && (newSinger->numberOfSongsEntered == 0);

		if (m_maxSingerRepeats && newSinger->numberOfSongsInQueue >= m_maxSingerRepeats)
		{
			entryIndex = 0xFFFFFFFFL;
			newSingerTargetRotation = OUT_OF_ROTATION;
			return;
		}

		// Here is the intelligent add. Go through the Karaoke rotation algorithm.
		bool bQueueLockFound = false;
		entryIndex = 0;

		// For any singer, we know:
		//
		// - number of songs they entered through the evening
		// - number of songs they had already sung (includes songs being sung at the moment)
		// - number of songs they have in the queue
		// - the rotation of the last song that they entered
		// - their index within a rotation (where they are in line, with respect to other singers)
		//
		// For any existing song entry, we know:
		// - whether the song has been played (includes song being played at the moment)
		// - which rotation it is in
		// - is it a singer's first song
		//
		// The target rotation for the given song needs to be based on:
		// - The current rotation value
		// - The number of songs the singer has sung thus far (0 or non-0)
		// - The number of songs that are queued

		// If the user entered more entries than the number of rotations that had completed
		// they are queueing up. So, base the target rotation entry of this song on the number of
		// songs that were entered.

		if ((newSinger->numberOfSongsSung == 0) && (newSinger->numberOfSongsInQueue==0))
		{
			// VIRGIN! They go right in rotation.
			// This is the only exception. If a singer has sung once, they go into a subsequent rotation,
			// not the current rotation.
			newSingerTargetRotation = m_currentRotationValue;
		}

		// If the user has entered less entries than the number of rotations that had completed,
		// they are behind. To prevent such a user from suddenly queueing lots of entries, the
		// target rotation entry of this song should be the NEXT rotation at a MINIMUM, and then
		// one for every song they queue afterward.
		else
		{
			// In general, your target rotation value is the current value plus the number
			// of songs you have in the queue.
			unsigned long rv = m_currentRotationValue + newSinger->numberOfSongsInQueue;

			// This simply makes sure no user is queued twice in the same rotation
			if (newSinger->rotationLastEntered >= rv) rv = newSinger->rotationLastEntered+1;

			// This simply makes sure none of the above math adds more than 1 to the number of
			// rotations queued. Not sure if it is needed, here to be safe.
			if (rv > m_numRotationsQueued+1) rv = m_numRotationsQueued+1;

			// This is your number.
			newSingerTargetRotation = rv;
		}

		while (currSongListEntry)
		{
			stSingerEntry *currSinger = currSongListEntry->songEntry.singer;

			unsigned long currSingerTargetRotation  = currSongListEntry->songEntry.targetRotation;

			unsigned long newSingerTicketBumpValue  = newSongListEntry->songEntry.ticketBumpValue;
			unsigned long currSingerTicketBumpValue = currSongListEntry->songEntry.ticketBumpValue;

			unsigned long currEntryQueueLockType    = 0;
			unsigned long numSingers = GetNumSingers();

			IsSongEntryQueueLocked(entryIndex, &currEntryQueueLockType);

			// First rule... if this song is playing, skip
			if (currSongListEntry->songEntry.played == TRUE)
			{
			}

			// First rule... if the current song was marked as being LOWEST priority, insert now
			else if (IsSongEntryLowestPriority(&currSongListEntry->songEntry))
			{
				sprintf(chDebugMsg, "ENTER: Lowest Priority Detected: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u, as Song '%s' was marked as being lowest priority\n",
					newSinger->karaokeName,
					newSingerTargetRotation+1,
					newSongListEntry->songEntry.singer->rotationIndex+1,
					newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
					newSongListEntry->songEntry.songName,
					newSongListEntry->songEntry.ticketBumpValue,
					entryIndex+1,
					currSongListEntry->songEntry.songName
				);
				WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				break;
			}

			// Next rule... 5 tickets means you go ASAP
			else if ((newSingerTicketBumpValue >= 5) && (currSingerTicketBumpValue < 5))
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "ENTER: 5 Ticket Upgrade Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u\n",
						newSinger->karaokeName,
						newSingerTargetRotation+1,
						newSongListEntry->songEntry.singer->rotationIndex+1,
						newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
						newSongListEntry->songEntry.songName,
						newSongListEntry->songEntry.ticketBumpValue,
						entryIndex+1
					);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
				break;
			}

			// Next rule... what KDJ says goes.
			else if ((currSongListEntry->songEntry.kdjOverride == TRUE) ||
				     (currSingerTargetRotation == OUT_OF_ROTATION))
			{
				// This message is already in the debug file
				//sprintf(chDebugMsg, "SKIP: KDJ Override Priority: Current Song '%s' has been overridden by KDJ or Marked as Played\n", currSongListEntry->songEntry.songName);
				//WriteToDebugFile(chDebugMsg, strlen(chDebugMsg)+1);
			}

			// Next rule... 4 tickets means you go after any KDJ overrides
			else if ((newSingerTicketBumpValue >= 4) && (currSingerTicketBumpValue < 4))
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "ENTER: 4 Ticket Upgrade Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u\n",
						newSinger->karaokeName,
						newSingerTargetRotation+1,
						newSongListEntry->songEntry.singer->rotationIndex+1,
						newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
						newSongListEntry->songEntry.songName,
						newSongListEntry->songEntry.ticketBumpValue,
						entryIndex+1
					);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
				break;
			}

			// Next rule... automatic locks based on wait time
			else if (currEntryQueueLockType==QUEUE_LOCK_TIME)
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "SKIP: Wait Time Priority: Song '%s' (singer '%s', (%u, %u)) has been been queued for more than %u seconds\n",
						currSongListEntry->songEntry.songName,
						currSongListEntry->songEntry.singer->karaokeName,
						currSingerTargetRotation+1,
						currSongListEntry->songEntry.singer->rotationIndex+1,
						m_maximumSingerWaitTime);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
			}

			// Next rule... 3 tickets means you go after anyone waiting a long time
			else if ((newSingerTicketBumpValue >= 3) && (currSingerTicketBumpValue < 3))
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "ENTER: 3 Ticket Upgrade Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u\n",
						newSinger->karaokeName,
						newSingerTargetRotation+1,
						newSongListEntry->songEntry.singer->rotationIndex+1,
						newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
						newSongListEntry->songEntry.songName,
						newSongListEntry->songEntry.ticketBumpValue,
						entryIndex+1,
						newSongListEntry->songEntry.ticketBumpValue
					);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
				break;
			}

			// Next rule, any queue locks based on position in the queue
			else if (currEntryQueueLockType==QUEUE_LOCK_DYNAMIC)
			{
				if (bQueueLockFound==false)
				{
					if (debugFileEntry)
					{
						sprintf(chDebugMsg, "SKIP: Queue Lock Priority: Within queue lock range: %u entries are queued, the threshhold is %u songs, so skip up to the next %u entries\n",
							m_songsQueued,
							m_queueLockThreshhold,
							m_queueLockValue);
						WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
					}
					bQueueLockFound=true;
				}
			}
			else if (currEntryQueueLockType==QUEUE_LOCK_RELATIVE)
			{
				if (bQueueLockFound==false)
				{
					if (debugFileEntry)
					{
						sprintf(chDebugMsg, "SKIP: Queue Lock Priority: Within queue lock percentage: %u of %u entries are queued, within %u%%\n",
							entryIndex,
							m_songsQueued,
							m_queueLockPercentage);
						WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
					}
					bQueueLockFound=true;
				}
			}

			// Next rule... 2 or more ticket bumps dominate in order of number of bumps
			else if (newSingerTicketBumpValue > currSingerTicketBumpValue)
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "ENTER: Ticket Upgrade Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u\n",
						newSinger->karaokeName,
						newSingerTargetRotation+1,
						newSongListEntry->songEntry.singer->rotationIndex+1,
						newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
						newSongListEntry->songEntry.songName,
						newSongListEntry->songEntry.ticketBumpValue,
						entryIndex+1
					);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
				break;
			}

			// Next rule... virgins go before non virgins!
			// If this is your first time to sing club... you have to sing...
			// But only if the KDJ configures the system that way
			else if ((isNewSinger) &&
					 (currSinger->numberOfSongsSung != 0) &&
					 (currSinger->rotationIndex) >= (numSingers-1)*(100-m_newSingerPreferenceFactor)/100)
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "ENTER: New Singer Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u and has not yet sung, current singer '%s' (%u, %u) has entered %u songs and sung %u times, preference factor is %u%%\n",
						newSinger->karaokeName,
						newSingerTargetRotation+1,
						newSongListEntry->songEntry.singer->rotationIndex+1,
						newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
						newSongListEntry->songEntry.songName,
						newSongListEntry->songEntry.ticketBumpValue,
						entryIndex+1,
						currSinger->karaokeName,
						currSingerTargetRotation+1,
						currSongListEntry->songEntry.singer->rotationIndex+1,
						currSinger->numberOfSongsEntered+1,
						currSinger->numberOfSongsSung,
						m_newSingerPreferenceFactor
					);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
				break;
			}

			// Next rule... singers who have not yet sung in this rotation sing before those who have
			else if (newSingerTargetRotation < currSingerTargetRotation)
			{
				if (debugFileEntry)
				{
					sprintf(chDebugMsg, "ENTER: Target Rotation Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u, before singer '%s' (%u, %u)\n",
						newSinger->karaokeName,
						newSingerTargetRotation+1,
						newSongListEntry->songEntry.singer->rotationIndex+1,
						newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
						newSongListEntry->songEntry.songName,
						newSongListEntry->songEntry.ticketBumpValue,
						entryIndex+1,
						currSinger->karaokeName,
						currSingerTargetRotation+1,
						currSongListEntry->songEntry.singer->rotationIndex+1
					);
					WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
				}
				break;
			}

			// Next rule... rotation
			else if (newSingerTargetRotation == currSingerTargetRotation)
			{
				// OK, now we know we have matched target rotation items, we go by rules of rotation
				// But we make one exception, if new singers are preferred
				// If this is the current singer's FIRST time singing, and if this is NOT the
				// new singer's first time singing, the current singer gets priority. For this round
				// only, the current singer is allowed to go out of rotation
				if ((m_newSingerPreferenceFactor > 50) &&
					(currSongListEntry->songEntry.singerRepeatValue == 1) &&
					(newSinger->numberOfSongsEntered != 0))
				{
					if (debugFileEntry)
					{
						sprintf(chDebugMsg, "SKIP: New Singer Priority: Current Song '%s', position %u, is Singer '%s's (%u, %u) first song,\n  New Song '%s' is NOT New Singer '%s's (%u, %u) first song.\n Skipping.\n",
										currSongListEntry->songEntry.songName,
										entryIndex+1,
										currSongListEntry->songEntry.singer->karaokeName,
										currSingerTargetRotation+1,
										currSongListEntry->songEntry.singer->rotationIndex+1,
										newSongListEntry->songEntry.songName,
										newSongListEntry->songEntry.singer->karaokeName,
										newSingerTargetRotation+1,
										newSongListEntry->songEntry.singer->rotationIndex+1
								);
						WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
					}
				}

				else if (newSinger->rotationIndex < currSinger->rotationIndex)
				{
					if (debugFileEntry)
					{
						sprintf(chDebugMsg, "ENTER: Rotation Index Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u, ahead of previous singer '%s' (%u, %u)\n",
							newSinger->karaokeName,
							newSingerTargetRotation+1,
							newSongListEntry->songEntry.singer->rotationIndex+1,
							newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
							newSongListEntry->songEntry.songName,
							newSongListEntry->songEntry.ticketBumpValue,
							entryIndex+1,
							currSinger->karaokeName,
							currSingerTargetRotation+1,
							currSinger->rotationIndex+1
						);
						WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
					}
					break;
				}
			}

			entryIndex++;
			prevSongListEntry=currSongListEntry;
			currSongListEntry=currSongListEntry->next;
		}
	}  // Intelligent add

	if (p) *p = prevSongListEntry;
	if (c) *c = currSongListEntry;
}

stSongEntry* CKaraokeRotation::AddSong(stSongEntry *song, stSingerEntry *newSinger, unsigned long &entryIndex, bool addToSpecifiedPosition)
{
	if (!song) return NULL;

	stSongListEntry *newSongListEntry = new stSongListEntry;
	if (!newSongListEntry)
	{
		return NULL;
	}

	newSinger = AddSinger(newSinger);

	memset(newSongListEntry, 0, sizeof(stSongListEntry));
	memcpy(&newSongListEntry->songEntry, song, sizeof(stSongEntry));

	newSongListEntry->songEntry.singer = newSinger;
	stSongListEntry *prevSongListEntry = NULL;
	stSongListEntry *currSongListEntry = NULL;
	unsigned long newSingerTargetRotation = 0;

	_getEnqueuePlaylistPosition(newSongListEntry, newSinger, entryIndex, &prevSongListEntry, &currSongListEntry, newSingerTargetRotation, addToSpecifiedPosition, TRUE);

	if (entryIndex == 0xFFFFFFFF)
	{
		delete newSongListEntry;
		return NULL;
	}

	char chDebugMsg[DEBUG_MESSAGE_SIZE];

	// If this song brings us into a new rotation, increment the rotation number
	if ((newSingerTargetRotation != OUT_OF_ROTATION) &&
		(newSingerTargetRotation > m_numRotationsQueued))
	{
		m_numRotationsQueued = newSingerTargetRotation;
		sprintf(chDebugMsg, "NOTE : The following song entry is the beginning of rotation %u\n",
				m_numRotationsQueued+1);
		WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
	}

	if (prevSongListEntry == NULL)
	{
		sprintf(chDebugMsg, "ENTER: Initial Singer: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u\n",
			newSongListEntry->songEntry.singer->karaokeName,
			newSingerTargetRotation+1,
			newSongListEntry->songEntry.singer->rotationIndex+1,
			newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
			newSongListEntry->songEntry.songName,
			newSongListEntry->songEntry.ticketBumpValue,
			entryIndex+1
		);
		WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));

		m_songListHead = newSongListEntry;
		newSongListEntry->next = currSongListEntry;
	}
	else
	{
		if ((currSongListEntry == NULL) && (addToSpecifiedPosition == FALSE))
		{
			// This is a condition where a debug file entry had not been written, so write one here
			sprintf(chDebugMsg, "ENTER: Last Priority: Singer '%s' (%u, %u) (%u time singing, song '%s', %u bump) has been entered at position %u\n",
				newSongListEntry->songEntry.singer->karaokeName,
				newSingerTargetRotation+1,
				newSongListEntry->songEntry.singer->rotationIndex+1,
				newSongListEntry->songEntry.singer->numberOfSongsEntered+1,
				newSongListEntry->songEntry.songName,
				newSongListEntry->songEntry.ticketBumpValue,
				entryIndex+1
				);
			WriteToDebugFile(chDebugMsg, strlen(chDebugMsg));
		}
		prevSongListEntry->next = newSongListEntry;
		newSongListEntry->next = currSongListEntry;
	}

	newSongListEntry->songEntry.targetRotation    = newSingerTargetRotation;
	newSongListEntry->songEntry.singerRepeatValue = newSinger->numberOfSongsEntered+1;
	m_songsQueued++;
	_addToSongQueuedList(&newSongListEntry->songEntry);

	// MG Note: Do this here???
	// _getTimeNow(newSongListEntry->songEntry.timeEntered);

	if (newSinger != &m_PlaceholderSinger)
	{
		newSinger->numberOfSongsEntered++;
		newSinger->numberOfSongsInQueue++;

		// Now, flag this rotation as the one where the user last entered a rotation value.
		newSinger->rotationLastEntered = newSingerTargetRotation;

		//sprintf(newSongListEntry->songEntry.songName, "Song %u", m_songsQueued);
		WriteSingerList();
	}
	else
	{
		WriteRotationList();
	}

	_updateSingerRepeatValues(newSinger);
	return &newSongListEntry->songEntry;
}

Zug's XML info:


 <?xml version="1.0" ?>
- <song_list version="2.1">
- <global>
  <numRotationsQueued value="0" />
  <songsQueued value="1" />
  <currentRotationValue value="0" />
  </global>
- <song>
  <artistName value="Kid Rock" />
  <songUID1 value="8" />
  <songUID2 value="2147483647" />
  <standby value="0" />
  <fileName value="C:\Documents and Settings\GX280\My Documents\Newsbin Download\ZMISC\Kid Rock - Only God Knows Why.MP3" />
  <kdjOverride value="0" />
  <played value="0" />
  <ticketsReceived value="1" />
  <singerRepeatValue value="1" />
  <songName value="Only God Knows Why" />
  <targetRotation value="0" />
  <ticketBumpValue value="0" />
  <timeEnteredHigh value="0" />
  <timeEnteredLow value="0" />
  <lengthInSeconds value="261" />
  <pitchTweak value="0" />
  <tempoTweak value="0" />
  <karaokeName value="Mike" />
  <onlineName value="" />
  </song>
  </song_list>