Mercurial > hg > beatroot
view at/ofai/music/util/MatchTempoMap.java @ 5:bcb4c9697967 tip
Add README and CITATION files
author | Chris Cannam |
---|---|
date | Tue, 03 Dec 2013 12:58:05 +0000 |
parents | 4c3f5bc01c97 |
children |
line wrap: on
line source
/* 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 at.ofai.music.util; import at.ofai.music.util.Format; // Stores a list of corresponding performance (real) times and // notated (score or MIDI) times. Automatically resizes when necessary. // Very inefficient if the list is edited after being used. // Assumes a monotonic mapping (t1 > t2 <=> s1 > s2) public class MatchTempoMap implements TempoMap { protected double[] realTime; // in seconds protected double[] scoreTime; // in beats or MIDI units protected int[] repeats; // for calculating the average if needed protected int size; // number of entries being used public MatchTempoMap() { this(5000); // Mozart files are up to 3251 notes } // default constructor public MatchTempoMap(int sz) { realTime = new double[sz]; scoreTime = new double[sz]; repeats = new int[sz]; size = 0; } // constructor protected void makeSpace() { if (size == realTime.length) resize(new MatchTempoMap(2 * size)); } // makeSpace() protected void closeList() { if (size != realTime.length) resize(new MatchTempoMap(size)); } // closeList() protected void resize(MatchTempoMap newList) { for (int i = 0; i < size; i++) { newList.realTime[i] = realTime[i]; newList.scoreTime[i] = scoreTime[i]; newList.repeats[i] = repeats[i]; } realTime = newList.realTime; scoreTime = newList.scoreTime; repeats = newList.repeats; } // resize() public double toRealTime(double sTime) { closeList(); return lookup(sTime, scoreTime, realTime); } // toRealTime() public double toScoreTime(double rTime) { closeList(); return lookup(rTime, realTime, scoreTime); } // toScoreTime() public double lookup(double value, double[] domain, double[] range) { int index = java.util.Arrays.binarySearch(domain, value); if (index >= 0) return range[index]; if ((size == 0) || ((size == 1) && ((range[0] == 0) || (domain[0] == 0)))) throw new RuntimeException("Insufficient entries in tempo map"); if (size == 1) return value * range[0] / domain[0]; index = -1 - index; // do linear interpolation if (index == 0) // unless at ends, where it is extrapolation index++; else if (index == size) index--; return (range[index] * (value - domain[index - 1]) + range[index - 1] * (domain[index] - value)) / (domain[index] - domain[index - 1]); } // lookup() public void add(double rTime, double sTime) { if (Double.isNaN(sTime)) return; makeSpace(); int index; for (index = 0; index < size; index++) if (sTime <= scoreTime[index]) break; if ((index == size) || (sTime != scoreTime[index])) { for (int j = size; j > index; j--) { scoreTime[j] = scoreTime[j-1]; realTime[j] = realTime[j-1]; repeats[j] = repeats[j-1]; } size++; scoreTime[index] = sTime; realTime[index] = rTime; repeats[index] = 1; } else { // average time of multiple nominally simultaneous notes realTime[index] = (repeats[index] * realTime[index] + rTime) / (repeats[index] + 1); repeats[index]++; } } // add() public void dump(double[] tempo, double step) { if (size < 2) { System.err.println("dump() failed: Empty tempo map"); return; } double[] tmp = new double[tempo.length]; int i = 0; for (int j = 1; j < size; j++) for ( ; i * step < realTime[j]; i++) tmp[i] = (realTime[j] - realTime[j - 1]) / (scoreTime[j] - scoreTime[j - 1]); for ( ; i < tmp.length; i++) tmp[i] = (realTime[size - 1] - realTime[size - 2]) / (scoreTime[size - 1] - scoreTime[size - 2]); int window = (int)(0.1 / step); // smooth over 2.0 second window double sum = 0; for (i = 0; i < tmp.length; i++) { sum += tmp[i]; if (i >= window) { sum -= tmp[i - window]; tempo[i] = sum / window; } else tempo[i] = sum / (i + 1); // System.out.println(i + " " + Format.d(tmp[i],3) + // " " + Format.d(tempo[i], 3)); if (tempo[i] != 0) tempo[i] = 60.0 / tempo[i]; } } // dump public void print() { System.out.println("Score | Perf.\n-------+-------"); for (int i = 0; i < size; i++) System.out.println(Format.d(scoreTime[i], 3) + " | " + Format.d(realTime[i], 3)); } // print() public static void main(String[] args) { // unit test TempoMap mtm = new MatchTempoMap(); mtm.add(0.6, 1); mtm.add(0.8, 2); mtm.add(0.95, 2.5); mtm.add(1.0, 3); double[] st = {0, 1, 2, 3, 4}; for (int i = 0 ; i < st.length; i++) System.out.println(st[i] + " -> " + mtm.toRealTime(st[i]) + " -> " + mtm.toScoreTime(mtm.toRealTime(st[i]))); double[] rt = {0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1}; for (int i = 0 ; i < rt.length; i++) System.out.println(rt[i] + " => " + mtm.toScoreTime(rt[i]) + " => " + mtm.toRealTime(mtm.toScoreTime(rt[i]))); } // main() } // class MatchTempoMap