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