annotate 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
rev   line source
Chris@2 1 /*
Chris@2 2 Copyright (C) 2001, 2006 by Simon Dixon
Chris@2 3
Chris@2 4 This program is free software; you can redistribute it and/or modify
Chris@2 5 it under the terms of the GNU General Public License as published by
Chris@2 6 the Free Software Foundation; either version 2 of the License, or
Chris@2 7 (at your option) any later version.
Chris@2 8
Chris@2 9 This program is distributed in the hope that it will be useful,
Chris@2 10 but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@2 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@2 12 GNU General Public License for more details.
Chris@2 13
Chris@2 14 You should have received a copy of the GNU General Public License along
Chris@2 15 with this program (the file gpl.txt); if not, download it from
Chris@2 16 http://www.gnu.org/licenses/gpl.txt or write to the
Chris@2 17 Free Software Foundation, Inc.,
Chris@2 18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Chris@2 19 */
Chris@2 20
Chris@2 21 package at.ofai.music.util;
Chris@2 22
Chris@2 23 import at.ofai.music.util.Format;
Chris@2 24
Chris@2 25 // Stores a list of corresponding performance (real) times and
Chris@2 26 // notated (score or MIDI) times. Automatically resizes when necessary.
Chris@2 27 // Very inefficient if the list is edited after being used.
Chris@2 28 // Assumes a monotonic mapping (t1 > t2 <=> s1 > s2)
Chris@2 29 public class MatchTempoMap implements TempoMap {
Chris@2 30
Chris@2 31 protected double[] realTime; // in seconds
Chris@2 32 protected double[] scoreTime; // in beats or MIDI units
Chris@2 33 protected int[] repeats; // for calculating the average if needed
Chris@2 34 protected int size; // number of entries being used
Chris@2 35
Chris@2 36 public MatchTempoMap() {
Chris@2 37 this(5000); // Mozart files are up to 3251 notes
Chris@2 38 } // default constructor
Chris@2 39
Chris@2 40 public MatchTempoMap(int sz) {
Chris@2 41 realTime = new double[sz];
Chris@2 42 scoreTime = new double[sz];
Chris@2 43 repeats = new int[sz];
Chris@2 44 size = 0;
Chris@2 45 } // constructor
Chris@2 46
Chris@2 47 protected void makeSpace() {
Chris@2 48 if (size == realTime.length)
Chris@2 49 resize(new MatchTempoMap(2 * size));
Chris@2 50 } // makeSpace()
Chris@2 51
Chris@2 52 protected void closeList() {
Chris@2 53 if (size != realTime.length)
Chris@2 54 resize(new MatchTempoMap(size));
Chris@2 55 } // closeList()
Chris@2 56
Chris@2 57 protected void resize(MatchTempoMap newList) {
Chris@2 58 for (int i = 0; i < size; i++) {
Chris@2 59 newList.realTime[i] = realTime[i];
Chris@2 60 newList.scoreTime[i] = scoreTime[i];
Chris@2 61 newList.repeats[i] = repeats[i];
Chris@2 62 }
Chris@2 63 realTime = newList.realTime;
Chris@2 64 scoreTime = newList.scoreTime;
Chris@2 65 repeats = newList.repeats;
Chris@2 66 } // resize()
Chris@2 67
Chris@2 68 public double toRealTime(double sTime) {
Chris@2 69 closeList();
Chris@2 70 return lookup(sTime, scoreTime, realTime);
Chris@2 71 } // toRealTime()
Chris@2 72
Chris@2 73 public double toScoreTime(double rTime) {
Chris@2 74 closeList();
Chris@2 75 return lookup(rTime, realTime, scoreTime);
Chris@2 76 } // toScoreTime()
Chris@2 77
Chris@2 78 public double lookup(double value, double[] domain, double[] range) {
Chris@2 79 int index = java.util.Arrays.binarySearch(domain, value);
Chris@2 80 if (index >= 0)
Chris@2 81 return range[index];
Chris@2 82 if ((size == 0) || ((size == 1) &&
Chris@2 83 ((range[0] == 0) || (domain[0] == 0))))
Chris@2 84 throw new RuntimeException("Insufficient entries in tempo map");
Chris@2 85 if (size == 1)
Chris@2 86 return value * range[0] / domain[0];
Chris@2 87 index = -1 - index; // do linear interpolation
Chris@2 88 if (index == 0) // unless at ends, where it is extrapolation
Chris@2 89 index++;
Chris@2 90 else if (index == size)
Chris@2 91 index--;
Chris@2 92 return (range[index] * (value - domain[index - 1]) +
Chris@2 93 range[index - 1] * (domain[index] - value)) /
Chris@2 94 (domain[index] - domain[index - 1]);
Chris@2 95 } // lookup()
Chris@2 96
Chris@2 97 public void add(double rTime, double sTime) {
Chris@2 98 if (Double.isNaN(sTime))
Chris@2 99 return;
Chris@2 100 makeSpace();
Chris@2 101 int index;
Chris@2 102 for (index = 0; index < size; index++)
Chris@2 103 if (sTime <= scoreTime[index])
Chris@2 104 break;
Chris@2 105 if ((index == size) || (sTime != scoreTime[index])) {
Chris@2 106 for (int j = size; j > index; j--) {
Chris@2 107 scoreTime[j] = scoreTime[j-1];
Chris@2 108 realTime[j] = realTime[j-1];
Chris@2 109 repeats[j] = repeats[j-1];
Chris@2 110 }
Chris@2 111 size++;
Chris@2 112 scoreTime[index] = sTime;
Chris@2 113 realTime[index] = rTime;
Chris@2 114 repeats[index] = 1;
Chris@2 115 } else { // average time of multiple nominally simultaneous notes
Chris@2 116 realTime[index] = (repeats[index] * realTime[index] + rTime) /
Chris@2 117 (repeats[index] + 1);
Chris@2 118 repeats[index]++;
Chris@2 119 }
Chris@2 120 } // add()
Chris@2 121
Chris@2 122 public void dump(double[] tempo, double step) {
Chris@2 123 if (size < 2) {
Chris@2 124 System.err.println("dump() failed: Empty tempo map");
Chris@2 125 return;
Chris@2 126 }
Chris@2 127 double[] tmp = new double[tempo.length];
Chris@2 128 int i = 0;
Chris@2 129 for (int j = 1; j < size; j++)
Chris@2 130 for ( ; i * step < realTime[j]; i++)
Chris@2 131 tmp[i] = (realTime[j] - realTime[j - 1]) /
Chris@2 132 (scoreTime[j] - scoreTime[j - 1]);
Chris@2 133 for ( ; i < tmp.length; i++)
Chris@2 134 tmp[i] = (realTime[size - 1] - realTime[size - 2]) /
Chris@2 135 (scoreTime[size - 1] - scoreTime[size - 2]);
Chris@2 136 int window = (int)(0.1 / step); // smooth over 2.0 second window
Chris@2 137 double sum = 0;
Chris@2 138 for (i = 0; i < tmp.length; i++) {
Chris@2 139 sum += tmp[i];
Chris@2 140 if (i >= window) {
Chris@2 141 sum -= tmp[i - window];
Chris@2 142 tempo[i] = sum / window;
Chris@2 143 } else
Chris@2 144 tempo[i] = sum / (i + 1);
Chris@2 145 // System.out.println(i + " " + Format.d(tmp[i],3) +
Chris@2 146 // " " + Format.d(tempo[i], 3));
Chris@2 147 if (tempo[i] != 0)
Chris@2 148 tempo[i] = 60.0 / tempo[i];
Chris@2 149 }
Chris@2 150 } // dump
Chris@2 151
Chris@2 152 public void print() {
Chris@2 153 System.out.println("Score | Perf.\n-------+-------");
Chris@2 154 for (int i = 0; i < size; i++)
Chris@2 155 System.out.println(Format.d(scoreTime[i], 3) + " | " +
Chris@2 156 Format.d(realTime[i], 3));
Chris@2 157 } // print()
Chris@2 158
Chris@2 159 public static void main(String[] args) { // unit test
Chris@2 160 TempoMap mtm = new MatchTempoMap();
Chris@2 161 mtm.add(0.6, 1);
Chris@2 162 mtm.add(0.8, 2);
Chris@2 163 mtm.add(0.95, 2.5);
Chris@2 164 mtm.add(1.0, 3);
Chris@2 165 double[] st = {0, 1, 2, 3, 4};
Chris@2 166 for (int i = 0 ; i < st.length; i++)
Chris@2 167 System.out.println(st[i] + " -> " + mtm.toRealTime(st[i]) +
Chris@2 168 " -> " + mtm.toScoreTime(mtm.toRealTime(st[i])));
Chris@2 169 double[] rt = {0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1};
Chris@2 170 for (int i = 0 ; i < rt.length; i++)
Chris@2 171 System.out.println(rt[i] + " => " + mtm.toScoreTime(rt[i]) +
Chris@2 172 " => " + mtm.toRealTime(mtm.toScoreTime(rt[i])));
Chris@2 173 } // main()
Chris@2 174
Chris@2 175 } // class MatchTempoMap