All Downloads are FREE. Search and download functionalities are using the official Maven repository.

be.tarsos.dsp.beatroot.AgentList Maven / Gradle / Ivy

There is a newer version: 2.4-1
Show newest version
/*
*      _______                       _____   _____ _____  
*     |__   __|                     |  __ \ / ____|  __ \ 
*        | | __ _ _ __ ___  ___  ___| |  | | (___ | |__) |
*        | |/ _` | '__/ __|/ _ \/ __| |  | |\___ \|  ___/ 
*        | | (_| | |  \__ \ (_) \__ \ |__| |____) | |     
*        |_|\__,_|_|  |___/\___/|___/_____/|_____/|_|     
*                                                         
* -------------------------------------------------------------
*
* TarsosDSP is developed by Joren Six at IPEM, University Ghent
*  
* -------------------------------------------------------------
*
*  Info: http://0110.be/tag/TarsosDSP
*  Github: https://github.com/JorenSix/TarsosDSP
*  Releases: http://0110.be/releases/TarsosDSP/
*  
*  TarsosDSP includes modified source code by various authors,
*  for credits and info, see README.
* 
*/

/*  BeatRoot: An interactive beat tracking system
    Copyright (C) 2001, 2006 by Simon Dixon

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program (the file gpl.txt); if not, download it from
	http://www.gnu.org/licenses/gpl.txt or write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

package be.tarsos.dsp.beatroot;

import java.util.ListIterator;


/** Class for maintaining the set of all Agents involved in beat tracking a piece of music.
 *  Implements a simple linked list terminated by an AgentList with a null Agent (ag).
 */
public class AgentList {

	/** Flag for choice between sum and average beat salience values for Agent scores.
	 *  The use of summed saliences favours faster tempi or lower metrical levels. */
	public static boolean useAverageSalience = false;
	
	/** Flag for printing debugging output. */
	public static boolean debug = false;
	
	/** For the purpose of removing duplicate agents, the default JND of IBI */
	public static final double DEFAULT_BI = 0.02;
	
	/** For the purpose of removing duplicate agents, the default JND of phase */
	public static final double DEFAULT_BT = 0.04;

	/** A beat tracking Agent */
	public Agent ag;
	
	/** The remainder of the linked list */
	public AgentList next;
	
	/** The length of the list (number of beat tracking Agents) */
	public static int count = 0;
	
	/** For the purpose of removing duplicate agents, the JND of IBI.
	 *  Not changed in the current version. */
	public static double thresholdBI = DEFAULT_BI;

	/** For the purpose of removing duplicate agents, the JND of phase.
	 *  Not changed in the current version. */
	public static double thresholdBT = DEFAULT_BT;

	/** Default constructor */
	public AgentList() {
		this(null, null);
	}
	
	/** Constructor for an AgentList: the Agent a is prepended to the list al.
	 *  @param a The Agent at the head of the list
	 *  @param al The tail of the list
	 */
	public AgentList(Agent a, AgentList al) {
		ag = a;
		next = al;
		if (next == null) {
			if (ag != null)
				next = new AgentList();		// insert null-terminator if it was forgotten
			else {
				count = 0;
				thresholdBI = DEFAULT_BI;
				thresholdBT = DEFAULT_BT;
			}
		}
	} // constructor

	/** Deep print of AgentList for debugging */
	public void print() {
		System.out.println("agentList.print: (size=" + count + ")");
		for (AgentList ptr = this; ptr.ag != null; ptr = ptr.next)
			ptr.ag.print(2);
		System.out.println("End of agentList.print()");
	} // print()

	/**
	 *  Inserts newAgent into the list in ascending order of beatInterval
	 * @param a
	 */
	public void add(Agent a) {
		add(a, true);
	} // add()/1

	/** Appends newAgent to list (sort==false), or inserts newAgent into the list
	 *  in ascending order of beatInterval
	 *  @param newAgent The agent to be added to the list
	 *  @param sort Flag indicating whether the list is sorted or not
	 */
	public void add(Agent newAgent, boolean sort){
		if (newAgent == null)
			return;
		AgentList ptr;
		count++;
		for (ptr = this; ptr.ag != null; ptr = ptr.next)
			if (sort && (newAgent.beatInterval <= ptr.ag.beatInterval)) {
				ptr.next = new AgentList(ptr.ag, ptr.next);
				ptr.ag = newAgent;
				return;
			}
		ptr.next = new AgentList();
		ptr.ag = newAgent;
	} // add()/2

	/** Sorts the AgentList by increasing beatInterval, using a bubble sort
	 *  since it is assumed that the list is almost sorted. */
	public void sort() {
		boolean sorted = false;
		while (!sorted) {
			sorted = true;
			for (AgentList ptr = this; ptr.ag != null; ptr = ptr.next) {
				if ((ptr.next.ag != null) &&
						(ptr.ag.beatInterval > ptr.next.ag.beatInterval)) {
					Agent temp = ptr.ag;
					ptr.ag = ptr.next.ag;
					ptr.next.ag = temp;
					sorted = false;
				}
			} // for
		} // while
	} // sort()

	/** Removes the current item from the list.
	 *  The current item does not need to be the head of the whole list.
	 *  @param ptr Points to the Agent which is removed from the list
	 */
	public void remove(AgentList ptr) {
		count--;
		ptr.ag = ptr.next.ag;	// null-terminated list always has next
		ptr.next = ptr.next.next;
	} // remove()

	/** Removes Agents from the list which are duplicates of other Agents.
	 *  A duplicate is defined by the tempo and phase thresholds
	 *  thresholdBI and thresholdBT respectively.
	 */
	protected void removeDuplicates() {
		sort();
		for (AgentList ptr = this; ptr.ag != null; ptr = ptr.next) {
			if (ptr.ag.phaseScore < 0.0)		// already flagged for deletion
				continue;
			for (AgentList ptr2 = ptr.next; ptr2.ag != null; ptr2 = ptr2.next) {
				if (ptr2.ag.beatInterval - ptr.ag.beatInterval > thresholdBI)
					break;
				if (Math.abs(ptr.ag.beatTime - ptr2.ag.beatTime) > thresholdBT)
					continue;
				if (ptr.ag.phaseScore < ptr2.ag.phaseScore) {
					ptr.ag.phaseScore = -1.0;	// flag for deletion
					if (ptr2.ag.topScoreTime < ptr.ag.topScoreTime)
						ptr2.ag.topScoreTime = ptr.ag.topScoreTime;
					break;
				} else {
					ptr2.ag.phaseScore = -1.0;	// flag for deletion
					if (ptr.ag.topScoreTime < ptr2.ag.topScoreTime)
						ptr.ag.topScoreTime = ptr2.ag.topScoreTime;
				}
			}
		}
		for (AgentList ptr = this; ptr.ag != null; ) {
			if (ptr.ag.phaseScore < 0.0) {
				remove(ptr);
			} else
				ptr = ptr.next;
		}
	} // removeDuplicates()

	/** Perform beat tracking on a list of events (onsets).
	 *  @param el The list of onsets (or events or peaks) to beat track
	 */
	public void beatTrack(EventList el) {
		beatTrack(el, -1.0);
	} // beatTrack()/1
	
	/** Perform beat tracking on a list of events (onsets).
	 *  @param el The list of onsets (or events or peaks) to beat track.
	 *  @param stop Do not find beats after stop seconds.
	 */
	public void beatTrack(EventList el, double stop) {
		ListIterator ptr = el.listIterator();
		boolean phaseGiven = (ag != null) &&
							 (ag.beatTime >= 0); // if given for one, assume given for others
		while (ptr.hasNext()) {
			Event ev = ptr.next();
			if ((stop > 0) && (ev.keyDown > stop))
				break;
			boolean created = phaseGiven;
			double prevBeatInterval = -1.0;
			for (AgentList ap = this; ap.ag != null; ap = ap.next) {
				Agent currentAgent = ap.ag;
				if (currentAgent.beatInterval != prevBeatInterval) {
					if ((prevBeatInterval>=0) && !created && (ev.keyDown<5.0)) {
						// Create new agent with different phase
						Agent newAgent = new Agent(prevBeatInterval);
						newAgent.considerAsBeat(ev, this);
						add(newAgent);
					}
					prevBeatInterval = currentAgent.beatInterval;
					created = phaseGiven;
				}
				if (currentAgent.considerAsBeat(ev, this))
					created = true;
				if (currentAgent != ap.ag)	// new one been inserted, skip it
					ap = ap.next;
			} // loop for each agent
			removeDuplicates();
		} // loop for each event
	} // beatTrack()

	/** Finds the Agent with the highest score in the list.
	 *  @return The Agent with the highest score
	 */
	public Agent bestAgent() {
		double best = -1.0;
		Agent bestAg = null;
		for (AgentList ap = this; ap.ag != null; ap = ap.next) {
			double startTime = ap.ag.events.l.getFirst().keyDown;
			double conf = (ap.ag.phaseScore + ap.ag.tempoScore) /
					(useAverageSalience? (double)ap.ag.beatCount: 1.0);
			if (conf > best) {
				bestAg = ap.ag;
				best = conf;
			}
			if (debug) {
				ap.ag.print(0);
				System.out.printf(" +%5.3f    Av-salience = %3.1f\n",
									startTime, conf);
			}
		}
		if (debug) {
			if (bestAg != null) {
				System.out.print("Best ");
				bestAg.print(0);
				System.out.printf("    Av-salience = %5.1f\n", best);
				// bestAg.events.print();
			} else
				System.out.println("No surviving agent - beat tracking failed");
		}
		return bestAg;
	} // bestAgent()

} // class AgentList




© 2015 - 2024 Weber Informatics LLC | Privacy Policy