plectrum

Plectrum: instrument tuner for Android
Log | Files | Refs | README | LICENSE

AgentList.java (9191B)


      1 /*
      2 *      _______                       _____   _____ _____  
      3 *     |__   __|                     |  __ \ / ____|  __ \ 
      4 *        | | __ _ _ __ ___  ___  ___| |  | | (___ | |__) |
      5 *        | |/ _` | '__/ __|/ _ \/ __| |  | |\___ \|  ___/ 
      6 *        | | (_| | |  \__ \ (_) \__ \ |__| |____) | |     
      7 *        |_|\__,_|_|  |___/\___/|___/_____/|_____/|_|     
      8 *                                                         
      9 * -------------------------------------------------------------
     10 *
     11 * TarsosDSP is developed by Joren Six at IPEM, University Ghent
     12 *  
     13 * -------------------------------------------------------------
     14 *
     15 *  Info: http://0110.be/tag/TarsosDSP
     16 *  Github: https://github.com/JorenSix/TarsosDSP
     17 *  Releases: http://0110.be/releases/TarsosDSP/
     18 *  
     19 *  TarsosDSP includes modified source code by various authors,
     20 *  for credits and info, see README.
     21 * 
     22 */
     23 
     24 /*  BeatRoot: An interactive beat tracking system
     25     Copyright (C) 2001, 2006 by Simon Dixon
     26 
     27     This program is free software; you can redistribute it and/or modify
     28     it under the terms of the GNU General Public License as published by
     29     the Free Software Foundation; either version 2 of the License, or
     30     (at your option) any later version.
     31 
     32     This program is distributed in the hope that it will be useful,
     33     but WITHOUT ANY WARRANTY; without even the implied warranty of
     34     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     35     GNU General Public License for more details.
     36 
     37     You should have received a copy of the GNU General Public License along
     38     with this program (the file gpl.txt); if not, download it from
     39 	http://www.gnu.org/licenses/gpl.txt or write to the Free Software Foundation, Inc.,
     40     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     41 */
     42 
     43 package be.tarsos.dsp.beatroot;
     44 
     45 import java.util.ListIterator;
     46 
     47 
     48 /** Class for maintaining the set of all Agents involved in beat tracking a piece of music.
     49  *  Implements a simple linked list terminated by an AgentList with a null Agent (ag).
     50  */
     51 public class AgentList {
     52 
     53 	/** Flag for choice between sum and average beat salience values for Agent scores.
     54 	 *  The use of summed saliences favours faster tempi or lower metrical levels. */
     55 	public static boolean useAverageSalience = false;
     56 	
     57 	/** Flag for printing debugging output. */
     58 	public static boolean debug = false;
     59 	
     60 	/** For the purpose of removing duplicate agents, the default JND of IBI */
     61 	public static final double DEFAULT_BI = 0.02;
     62 	
     63 	/** For the purpose of removing duplicate agents, the default JND of phase */
     64 	public static final double DEFAULT_BT = 0.04;
     65 
     66 	/** A beat tracking Agent */
     67 	public Agent ag;
     68 	
     69 	/** The remainder of the linked list */
     70 	public AgentList next;
     71 	
     72 	/** The length of the list (number of beat tracking Agents) */
     73 	public static int count = 0;
     74 	
     75 	/** For the purpose of removing duplicate agents, the JND of IBI.
     76 	 *  Not changed in the current version. */
     77 	public static double thresholdBI = DEFAULT_BI;
     78 
     79 	/** For the purpose of removing duplicate agents, the JND of phase.
     80 	 *  Not changed in the current version. */
     81 	public static double thresholdBT = DEFAULT_BT;
     82 
     83 	/** Default constructor */
     84 	public AgentList() {
     85 		this(null, null);
     86 	}
     87 	
     88 	/** Constructor for an AgentList: the Agent a is prepended to the list al.
     89 	 *  @param a The Agent at the head of the list
     90 	 *  @param al The tail of the list
     91 	 */
     92 	public AgentList(Agent a, AgentList al) {
     93 		ag = a;
     94 		next = al;
     95 		if (next == null) {
     96 			if (ag != null)
     97 				next = new AgentList();		// insert null-terminator if it was forgotten
     98 			else {
     99 				count = 0;
    100 				thresholdBI = DEFAULT_BI;
    101 				thresholdBT = DEFAULT_BT;
    102 			}
    103 		}
    104 	} // constructor
    105 
    106 	/** Deep print of AgentList for debugging */
    107 	public void print() {
    108 		System.out.println("agentList.print: (size=" + count + ")");
    109 		for (AgentList ptr = this; ptr.ag != null; ptr = ptr.next)
    110 			ptr.ag.print(2);
    111 		System.out.println("End of agentList.print()");
    112 	} // print()
    113 
    114 	/**
    115 	 *  Inserts newAgent into the list in ascending order of beatInterval
    116 	 * @param a
    117 	 */
    118 	public void add(Agent a) {
    119 		add(a, true);
    120 	} // add()/1
    121 
    122 	/** Appends newAgent to list (sort==false), or inserts newAgent into the list
    123 	 *  in ascending order of beatInterval
    124 	 *  @param newAgent The agent to be added to the list
    125 	 *  @param sort Flag indicating whether the list is sorted or not
    126 	 */
    127 	public void add(Agent newAgent, boolean sort){
    128 		if (newAgent == null)
    129 			return;
    130 		AgentList ptr;
    131 		count++;
    132 		for (ptr = this; ptr.ag != null; ptr = ptr.next)
    133 			if (sort && (newAgent.beatInterval <= ptr.ag.beatInterval)) {
    134 				ptr.next = new AgentList(ptr.ag, ptr.next);
    135 				ptr.ag = newAgent;
    136 				return;
    137 			}
    138 		ptr.next = new AgentList();
    139 		ptr.ag = newAgent;
    140 	} // add()/2
    141 
    142 	/** Sorts the AgentList by increasing beatInterval, using a bubble sort
    143 	 *  since it is assumed that the list is almost sorted. */
    144 	public void sort() {
    145 		boolean sorted = false;
    146 		while (!sorted) {
    147 			sorted = true;
    148 			for (AgentList ptr = this; ptr.ag != null; ptr = ptr.next) {
    149 				if ((ptr.next.ag != null) &&
    150 						(ptr.ag.beatInterval > ptr.next.ag.beatInterval)) {
    151 					Agent temp = ptr.ag;
    152 					ptr.ag = ptr.next.ag;
    153 					ptr.next.ag = temp;
    154 					sorted = false;
    155 				}
    156 			} // for
    157 		} // while
    158 	} // sort()
    159 
    160 	/** Removes the current item from the list.
    161 	 *  The current item does not need to be the head of the whole list.
    162 	 *  @param ptr Points to the Agent which is removed from the list
    163 	 */
    164 	public void remove(AgentList ptr) {
    165 		count--;
    166 		ptr.ag = ptr.next.ag;	// null-terminated list always has next
    167 		ptr.next = ptr.next.next;
    168 	} // remove()
    169 
    170 	/** Removes Agents from the list which are duplicates of other Agents.
    171 	 *  A duplicate is defined by the tempo and phase thresholds
    172 	 *  thresholdBI and thresholdBT respectively.
    173 	 */
    174 	protected void removeDuplicates() {
    175 		sort();
    176 		for (AgentList ptr = this; ptr.ag != null; ptr = ptr.next) {
    177 			if (ptr.ag.phaseScore < 0.0)		// already flagged for deletion
    178 				continue;
    179 			for (AgentList ptr2 = ptr.next; ptr2.ag != null; ptr2 = ptr2.next) {
    180 				if (ptr2.ag.beatInterval - ptr.ag.beatInterval > thresholdBI)
    181 					break;
    182 				if (Math.abs(ptr.ag.beatTime - ptr2.ag.beatTime) > thresholdBT)
    183 					continue;
    184 				if (ptr.ag.phaseScore < ptr2.ag.phaseScore) {
    185 					ptr.ag.phaseScore = -1.0;	// flag for deletion
    186 					if (ptr2.ag.topScoreTime < ptr.ag.topScoreTime)
    187 						ptr2.ag.topScoreTime = ptr.ag.topScoreTime;
    188 					break;
    189 				} else {
    190 					ptr2.ag.phaseScore = -1.0;	// flag for deletion
    191 					if (ptr.ag.topScoreTime < ptr2.ag.topScoreTime)
    192 						ptr.ag.topScoreTime = ptr2.ag.topScoreTime;
    193 				}
    194 			}
    195 		}
    196 		for (AgentList ptr = this; ptr.ag != null; ) {
    197 			if (ptr.ag.phaseScore < 0.0) {
    198 				remove(ptr);
    199 			} else
    200 				ptr = ptr.next;
    201 		}
    202 	} // removeDuplicates()
    203 
    204 	/** Perform beat tracking on a list of events (onsets).
    205 	 *  @param el The list of onsets (or events or peaks) to beat track
    206 	 */
    207 	public void beatTrack(EventList el) {
    208 		beatTrack(el, -1.0);
    209 	} // beatTrack()/1
    210 	
    211 	/** Perform beat tracking on a list of events (onsets).
    212 	 *  @param el The list of onsets (or events or peaks) to beat track.
    213 	 *  @param stop Do not find beats after <code>stop</code> seconds.
    214 	 */
    215 	public void beatTrack(EventList el, double stop) {
    216 		ListIterator<Event> ptr = el.listIterator();
    217 		boolean phaseGiven = (ag != null) &&
    218 							 (ag.beatTime >= 0); // if given for one, assume given for others
    219 		while (ptr.hasNext()) {
    220 			Event ev = ptr.next();
    221 			if ((stop > 0) && (ev.keyDown > stop))
    222 				break;
    223 			boolean created = phaseGiven;
    224 			double prevBeatInterval = -1.0;
    225 			for (AgentList ap = this; ap.ag != null; ap = ap.next) {
    226 				Agent currentAgent = ap.ag;
    227 				if (currentAgent.beatInterval != prevBeatInterval) {
    228 					if ((prevBeatInterval>=0) && !created && (ev.keyDown<5.0)) {
    229 						// Create new agent with different phase
    230 						Agent newAgent = new Agent(prevBeatInterval);
    231 						newAgent.considerAsBeat(ev, this);
    232 						add(newAgent);
    233 					}
    234 					prevBeatInterval = currentAgent.beatInterval;
    235 					created = phaseGiven;
    236 				}
    237 				if (currentAgent.considerAsBeat(ev, this))
    238 					created = true;
    239 				if (currentAgent != ap.ag)	// new one been inserted, skip it
    240 					ap = ap.next;
    241 			} // loop for each agent
    242 			removeDuplicates();
    243 		} // loop for each event
    244 	} // beatTrack()
    245 
    246 	/** Finds the Agent with the highest score in the list.
    247 	 *  @return The Agent with the highest score
    248 	 */
    249 	public Agent bestAgent() {
    250 		double best = -1.0;
    251 		Agent bestAg = null;
    252 		for (AgentList ap = this; ap.ag != null; ap = ap.next) {
    253 			double startTime = ap.ag.events.l.getFirst().keyDown;
    254 			double conf = (ap.ag.phaseScore + ap.ag.tempoScore) /
    255 					(useAverageSalience? (double)ap.ag.beatCount: 1.0);
    256 			if (conf > best) {
    257 				bestAg = ap.ag;
    258 				best = conf;
    259 			}
    260 			if (debug) {
    261 				ap.ag.print(0);
    262 				System.out.printf(" +%5.3f    Av-salience = %3.1f\n",
    263 									startTime, conf);
    264 			}
    265 		}
    266 		if (debug) {
    267 			if (bestAg != null) {
    268 				System.out.print("Best ");
    269 				bestAg.print(0);
    270 				System.out.printf("    Av-salience = %5.1f\n", best);
    271 				// bestAg.events.print();
    272 			} else
    273 				System.out.println("No surviving agent - beat tracking failed");
    274 		}
    275 		return bestAg;
    276 	} // bestAgent()
    277 
    278 } // class AgentList