# HG changeset patch
# User Chris Cannam
# Date 1286550227 -3600
# Node ID 88f50ba37174579b9928f041c13a81c0917efa76
# Parent 9feddf959b6b537f505c8b61c81989e698d76ea6
* Import MATCH v0.9.4
diff -r 9feddf959b6b -r 88f50ba37174 at/ofai/music/match/AudioFile.java
--- a/at/ofai/music/match/AudioFile.java Fri Oct 08 16:02:41 2010 +0100
+++ b/at/ofai/music/match/AudioFile.java Fri Oct 08 16:03:47 2010 +0100
@@ -85,13 +85,23 @@
public int search(int[] arr, int val) {
int max = pathLength - 1;
int min = 0;
- while (max > min) {
- int mid = (max + min) / 2;
- if (val > arr[mid])
- min = mid + 1;
- else
- max = mid;
- } // max = MIN_j (arr[j] >= val) i.e. the first equal or next highest
+ if ((max > min) && (arr[max] > arr[min])) {
+ while (max > min) {
+ int mid = (max + min) / 2;
+ if (val > arr[mid])
+ min = mid + 1;
+ else
+ max = mid;
+ } // max = MIN_j (arr[j] >= val) i.e. the first equal or next highest
+ } else { // elements in reverse order (bPath)
+ while (max > min) {
+ int mid = (max + min) / 2;
+ if (val < arr[mid])
+ min = mid + 1;
+ else
+ max = mid;
+ } // max = MIN_j (arr[j] <= val) i.e. the first equal or next lowest
+ }
while ((max + 1 < pathLength) && (arr[max + 1] == val))
max++;
return (min + max) / 2;
diff -r 9feddf959b6b -r 88f50ba37174 at/ofai/music/match/GUI.java
--- a/at/ofai/music/match/GUI.java Fri Oct 08 16:02:41 2010 +0100
+++ b/at/ofai/music/match/GUI.java Fri Oct 08 16:03:47 2010 +0100
@@ -30,7 +30,7 @@
MouseMotionListener,
KeyListener {
- public static final String version = "0.9.2";
+ public static final String version = "0.9.4";
public static final String title = "MATCH " + version;
protected static final int xSize = 230;
protected static final int ySize = 150;
diff -r 9feddf959b6b -r 88f50ba37174 at/ofai/music/match/MatrixFrame.java
--- a/at/ofai/music/match/MatrixFrame.java Fri Oct 08 16:02:41 2010 +0100
+++ b/at/ofai/music/match/MatrixFrame.java Fri Oct 08 16:03:47 2010 +0100
@@ -17,6 +17,7 @@
import at.ofai.music.util.Event;
import at.ofai.music.util.WormEvent;
import at.ofai.music.util.PSPrinter;
+import at.ofai.music.worm.WormFile;
class MatrixFrame extends JFrame implements MouseListener,
MouseMotionListener,
@@ -34,6 +35,7 @@
int bottom; // size of margin
int left; // size of margin
int right; // size of margin
+ public static boolean useSmoothPath = true;
public final Color BACKGROUND = Color.black;
public final Color FOREGROUND = Color.green;
public final int[] flagsToLength = { 0, 5,10,10,15,15,15,15,
@@ -46,11 +48,13 @@
sm = s;
bottom = defaultMargin;
left = defaultMargin;
- if (sm.pm2.hasMatchFile)
+ if ((sm.pm2.metadata == PerformanceMatcher.MetaType.MATCH) ||
+ (sm.pm2.metadata == PerformanceMatcher.MetaType.MIDI)) // hasMatchFile)
top = matchFileMargin;
else
top = defaultMargin;
- if (sm.pm1.hasMatchFile)
+ if ((sm.pm1.metadata == PerformanceMatcher.MetaType.MATCH) ||
+ (sm.pm1.metadata == PerformanceMatcher.MetaType.MIDI)) // hasMatchFile)
right = matchFileMargin;
else
right = defaultMargin;
@@ -62,17 +66,23 @@
sm.parent = this;
if (sm.pm1.events != null) {
eventIterator1 = sm.pm1.events.listIterator();
- if (sm.pm1.hasWormFile)
+ //if (sm.pm1.hasWormFile)
+ if (sm.pm1.metadata == PerformanceMatcher.MetaType.WORM)
generateLabels(eventIterator1);
- if (sm.pm1.hasMatchFile)
+ //if (sm.pm1.hasMatchFile)
+ if ((sm.pm1.metadata == PerformanceMatcher.MetaType.MATCH) ||
+ (sm.pm1.metadata == PerformanceMatcher.MetaType.MIDI))
correctTiming(eventIterator1, sm.pm1.matchFileOffset);
} else
eventIterator1 = null;
if (sm.pm2.events != null) {
eventIterator2 = sm.pm2.events.listIterator();
- if (sm.pm2.hasWormFile)
+ //if (sm.pm2.hasWormFile)
+ if (sm.pm2.metadata == PerformanceMatcher.MetaType.WORM)
generateLabels(eventIterator2);
- if (sm.pm2.hasMatchFile)
+ //if (sm.pm2.hasMatchFile)
+ if ((sm.pm2.metadata == PerformanceMatcher.MetaType.MATCH) ||
+ (sm.pm2.metadata == PerformanceMatcher.MetaType.MIDI))
correctTiming(eventIterator2, sm.pm2.matchFileOffset);
} else
eventIterator2 = null;
@@ -101,13 +111,15 @@
int bar = 0;
while (it.hasNext()) {
WormEvent w = (WormEvent) it.next();
- if ((w.flags & 4) != 0) {
+ if ((w.flags & WormFile.BAR) != 0) {
bar++;
- if ((w.flags & 8) != 0)
+ if (w.label != null) // don't overwrite existing labels e.g. sapp data
+ continue;
+ if ((w.flags & WormFile.SEG1) != 0)
w.label = "*" + bar;
else
w.label = "" + bar;
- if ((w.flags & 16) != 0)
+ if ((w.flags & WormFile.SEG2) != 0)
w.label += "*";
}
}
@@ -180,14 +192,20 @@
continue;
else if (x > wd - left - right)
break;
- addHMarker(g, e.label, x + left, flagsToLength[e.flags&15]);
+ if ((e.flags & WormFile.BAR) != 0)
+ addHMarker(g, e.label, x + left, flagsToLength[e.flags&15]);
+ else
+ addHMarker(g, null, x + left, flagsToLength[e.flags&15]);
}
} // addHWorm()
/** Displays tempo curve based on the OTHER file's WormFile. */
protected void addHTempoCurve(Graphics g) {
- map.setMatch(sm.sPathY, sm.hop1, sm.sPathX, sm.hop2,
+ if (useSmoothPath)
+ map.setMatch(sm.sPathY, sm.hop1, sm.sPathX, sm.hop2,
sm.sPathLength); // x is reference
+ else // note bPath is in the opposite order
+ map.setMatch(sm.bPathY, sm.hop1, sm.bPathX, sm.hop2, sm.bPathLength);
double t1 = 0;
while (eventIterator1.hasPrevious()) {
Event e = eventIterator1.previous();
@@ -198,8 +216,11 @@
int x1 = (int) Math.round(t1 / sm.hop1) + sm.x0;
int y1 = -1;
double stop = 0;
- if (sm.sPathLength > 0)
- stop = sm.sPathY[sm.sPathLength - 1] * sm.hop1;
+ if (useSmoothPath) {
+ if (sm.sPathLength > 0)
+ stop = sm.sPathY[sm.sPathLength - 1] * sm.hop1;
+ } else if (sm.bPathLength > 0)
+ stop = sm.bPathY[0] * sm.hop1; // bPath is in reverse order
while (eventIterator1.hasNext()) {
WormEvent e = (WormEvent) eventIterator1.next();
double t2 = map.toReferenceTimeD(e.keyDown);
@@ -216,7 +237,10 @@
continue;
else if (e.keyDown > stop)
break;
- addHMarker(g, e.label, x2 + left, flagsToLength[e.flags&15]);
+ if ((e.flags & WormFile.BAR) != 0)
+ addHMarker(g, e.label, x2 + left, flagsToLength[e.flags&15]);
+ else
+ addHMarker(g, null, x2 + left, flagsToLength[e.flags&15]);
if (y1 >= 0)
g.drawLine(x1 + left, y1, x2 + left, y2);
t1 = t2;
@@ -298,7 +322,10 @@
continue;
else if (y < 0)
break;
- addVMarker(g, e.label, y + top, flagsToLength[e.flags&15]);
+ if ((e.flags & WormFile.BAR) != 0)
+ addVMarker(g, e.label, y + top, flagsToLength[e.flags&15]);
+ else
+ addVMarker(g, null, y + top, flagsToLength[e.flags&15]);
}
} // addVWorm()
@@ -361,11 +388,16 @@
x += dx;
}
if (eventIterator2 != null) {
- if (sm.pm2.hasWormFile)
+ //if (sm.pm2.hasWormFile)
+ if (sm.pm2.metadata == PerformanceMatcher.MetaType.WORM)
addHWorm(g);
- if (sm.pm2.hasMatchFile)
+ //if (sm.pm2.hasMatchFile)
+ if ((sm.pm2.metadata == PerformanceMatcher.MetaType.MATCH) ||
+ (sm.pm2.metadata == PerformanceMatcher.MetaType.MIDI))
addHMatch(g);
- } else if ((eventIterator1 != null) && sm.pm1.hasWormFile) {
+ //} else if ((eventIterator1 != null) && sm.pm1.hasWormFile) {
+ } else if ((eventIterator1 != null) &&
+ (sm.pm1.metadata == PerformanceMatcher.MetaType.WORM)) {
// TODO: implement addHTempoCurve and equiv for V
// adds tempo curve based on worm
addHTempoCurve(g);
@@ -386,9 +418,12 @@
y += dy;
}
if (eventIterator1 != null) {
- if (sm.pm1.hasWormFile)
+ //if (sm.pm1.hasWormFile)
+ if (sm.pm1.metadata == PerformanceMatcher.MetaType.WORM)
addVWorm(g2);
- if (sm.pm1.hasMatchFile)
+ //if (sm.pm1.hasMatchFile)
+ if ((sm.pm1.metadata == PerformanceMatcher.MetaType.MATCH) ||
+ (sm.pm1.metadata == PerformanceMatcher.MetaType.MIDI))
addVMatch(g2);
}
g2.rotate(Math.PI / 2);
diff -r 9feddf959b6b -r 88f50ba37174 at/ofai/music/match/PerformanceMatcher.java
--- a/at/ofai/music/match/PerformanceMatcher.java Fri Oct 08 16:02:41 2010 +0100
+++ b/at/ofai/music/match/PerformanceMatcher.java Fri Oct 08 16:03:47 2010 +0100
@@ -1,5 +1,6 @@
package at.ofai.music.match;
+import java.awt.Color;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -19,11 +20,14 @@
import at.ofai.music.util.Event;
import at.ofai.music.util.EventList;
import at.ofai.music.util.Profile;
+import at.ofai.music.util.WormEvent;
+import at.ofai.music.worm.Plot;
/** Represents an audio stream that can be matched to another audio stream of
* the same piece of music. The matching algorithm uses dynamic time warping.
- * The distance metric is a Euclidean metric on the FFT data with the higher
- * frequencies mapped onto a linear scale.
+ * The distance metric is a Euclidean metric on the first difference of the
+ * magnitude spectrum with the lower frequencies on a linear scale and the
+ * higher frequencies mapped onto a logarithmic scale.
*/
public class PerformanceMatcher {
@@ -31,7 +35,7 @@
* The data for the distance metric and the dynamic time warping is shared
* between the two matchers. In the original version, only one of the two
* performance matchers contained the distance metric. (See
- * first
) */
+ * firstPM
) */
protected PerformanceMatcher otherMatcher;
/** Indicates which performance is considered primary (the score). This is
@@ -75,11 +79,18 @@
* the times and velocities of all notes (as recorded by the Boesendorfer
* SE290). */
protected String matchFileName;
- protected boolean hasWormFile;
- protected boolean hasMatchFile;
+ protected String outputFileName;
+ protected enum PathType {BACKWARD, FORWARD, SMOOTHED};
+ protected PathType outputType;
+ protected enum MetaType {NONE, MATCH, MIDI, WORM, LABEL};
+ protected MetaType metadata;
+ //protected boolean hasWormFile;
+ //protected boolean hasMatchFile;
+ //protected boolean hasLabelFile;
protected EventList events;
protected WormHandler wormHandler;
protected boolean liveWorm;
+ protected double referenceFrequency;
/** Onset time of the first note in the audio file, in order to establish
* synchronisation between the match file and the audio data. */
@@ -87,7 +98,7 @@
/** Flag (command line options -n1 and -N1) indicating whether
* or not each frame of audio should be normalised to have a sum of 1.
- * (Default = false). */
+ * (Default = true). */
protected boolean normalise1;
/** Flag (command line options -n2 and -N2) indicating whether
@@ -105,9 +116,15 @@
/** Flag (command line options -n4 and -N4) indicating whether
* or not the distance metric for pairs of audio frames should be
* normalised by the log of the sum of the frames.
- * (Default = false; assumes normalise2 == false). */
+ * (Default = true; assumes normalise2 == false). */
protected boolean normalise4;
+ /** Flag (command line options -n5 and -N5) indicating whether
+ * or not the distance metric for pairs of audio frames should be
+ * set to zero between annotated positions.
+ * (Default = false). */
+ protected boolean normalise5;
+
/** Flag (command line options -d and -D) indicating whether
* or not the half-wave rectified spectral difference should be used in
* calculating the distance metric for pairs of audio frames, instead of
@@ -129,7 +146,7 @@
/** The size of an FFT frame in seconds, as set by the command line option
* -f FFTTime. (Default = 0.04644s). Note that the value is not
* taken to be precise; it is adjusted so that fftSize
is
- * always power of 2. */
+ * always a power of 2. */
protected double fftTime;
/** The width of the search band (error margin) around the current match
@@ -223,6 +240,9 @@
/** The bounds of each row of data in the distance and path cost matrices.*/
protected int[] first, last;
+
+ /** The frames to ignore because they are not annotated. */
+ protected boolean[] ignore;
/** GUI component which shows progress of alignment. */
protected GUI.FileNameSelection progressCallback;
@@ -277,17 +297,23 @@
normalise2 = false;
normalise3 = false;
normalise4 = true;
+ normalise5 = false;
useSpectralDifference = true;
useChromaFrequencyMap = false;
audioOutputRequested = false;
scale = 90;
maxFrames = 0; // stop at EOF
progressCallback = null;
- hasMatchFile = false;
- hasWormFile = false;
+ metadata = MetaType.NONE;
+ //hasMatchFile = false;
+ //hasWormFile = false;
+ //hasLabelFile = false;
liveWorm = false;
matchFileName = null;
+ outputFileName = null;
+ outputType = PathType.BACKWARD;
events = null;
+ referenceFrequency = 440.0;
} // default constructor
/** For debugging, outputs information about the PerformanceMatcher to
@@ -332,14 +358,19 @@
public void setMatchFile(String fileName, double tStart, boolean isWorm) {
matchFileName = fileName;
matchFileOffset = tStart;
- hasWormFile = isWorm;
- hasMatchFile = !isWorm;
+ metadata = isWorm? MetaType.WORM: MetaType.MATCH;
+ //hasWormFile = isWorm;
+ //hasMatchFile = !isWorm;
try {
if (isWorm) {
setInputFile(EventList.getAudioFileFromWormFile(matchFileName));
events = EventList.readWormFile(matchFileName);
if (otherMatcher.wormHandler != null)
otherMatcher.wormHandler.init();
+ } else if (fileName.endsWith(".mid")) {
+ events = EventList.readMidiFile(matchFileName);
+ metadata = MetaType.MIDI;
+ //hasMatchFile = false;
} else
events = EventList.readMatchFile(matchFileName);
} catch (Exception e) {
@@ -352,6 +383,59 @@
setMatchFile(fileName, tStart, false);
} // setMatchFile()
+ public void setLabelFile(String fileName) {
+ //hasMatchFile = false;
+ //hasWormFile = true;
+ //hasLabelFile = true;
+ matchFileName = fileName;
+ metadata = MetaType.LABEL;
+ try {
+ events = EventList.readLabelFile(fileName);
+ } catch (Exception e) {
+ System.err.println("Error reading labelFile: " + fileName + "\n"+e);
+ events = null;
+ }
+ } // setLabelFile()
+
+ public void writeLabelFile(ScrollingMatrix sm) {
+ EventList el = new EventList();
+ AudioFile af = new AudioFile();
+ if (MatrixFrame.useSmoothPath)
+ af.setMatch(sm.sPathX, sm.hop2, sm.sPathY, sm.hop1, sm.sPathLength);
+ else
+ af.setMatch(sm.bPathX, sm.hop2, sm.bPathY, sm.hop1, sm.bPathLength);
+ for (Event ev: otherMatcher.events.l) {
+ WormEvent we = new WormEvent(af.fromReferenceTimeD(ev.keyDown),0,0,0,0);
+ we.label = ((WormEvent)ev).label;
+ el.add(we);
+ }
+ try {
+ el.writeLabelFile(matchFileName);
+ } catch (Exception e) {
+ System.err.println("Unable to write output file: " + e);
+ }
+ } // writeLabelFile()
+
+ public void writeMidiFile(ScrollingMatrix sm) {
+ EventList el = new EventList();
+ AudioFile af = new AudioFile();
+ if (MatrixFrame.useSmoothPath)
+ af.setMatch(sm.sPathX, sm.hop2, sm.sPathY, sm.hop1, sm.sPathLength);
+ else
+ af.setMatch(sm.bPathX, sm.hop2, sm.bPathY, sm.hop1, sm.bPathLength);
+ for (Event ev: otherMatcher.events.l) {
+ Event copy = ev.clone();
+ copy.keyDown = af.fromReferenceTimeD(ev.keyDown);
+ copy.keyUp = af.fromReferenceTimeD(ev.keyUp);
+ el.add(copy);
+ }
+ try {
+ el.writeMIDI(matchFileName);
+ } catch (Exception e) {
+ System.err.println("Unable to write output file: " + e);
+ }
+ } // writeMidiFile()
+
/** Sets up the streams and buffers for live audio input (CD quality).
* If any Exception is thrown within this method, it is caught, and any
* opened streams are closed, and pcmInputStream
is set to
@@ -426,7 +510,7 @@
fftSize = (int) Math.round(Math.pow(2,
Math.round( Math.log(fftTime * sampleRate) / Math.log(2))));
blockSize = (int) Math.round(blockTime / hopTime);
- makeFreqMap(fftSize, sampleRate);
+ makeFreqMap(fftSize, sampleRate, referenceFrequency);
int buffSize = hopSize * channels * 2;
if ((inputBuffer == null) || (inputBuffer.length != buffSize))
inputBuffer = new byte[buffSize];
@@ -449,6 +533,12 @@
bestPathCost = new int[len][];
first = new int[len];
last = new int[len];
+ if (normalise5 && (events != null)) { // skip frames without onsets
+ ignore = new boolean[len];
+ Arrays.fill(ignore, true);
+ for (Event e: events.l) //TODO: check if should be corrected
+ ignore[(int)Math.round(e.keyDown / hopTime)] = false;
+ }
for (int i = 0; i < blockSize; i++) {
distance[i] = new byte[(MAX_RUN_COUNT+1) * blockSize];
bestPathCost[i] = new int[(MAX_RUN_COUNT+1) * blockSize];
@@ -499,12 +589,12 @@
}
} // closeStreams()
- protected void makeFreqMap(int fftSize, float sampleRate) {
+ protected void makeFreqMap(int fftSize, float sampleRate, double refFreq) {
freqMap = new int[fftSize/2+1];
if (useChromaFrequencyMap)
- makeChromaFrequencyMap(fftSize, sampleRate);
+ makeChromaFrequencyMap(fftSize, sampleRate, refFreq);
else
- makeStandardFrequencyMap(fftSize, sampleRate);
+ makeStandardFrequencyMap(fftSize, sampleRate, refFreq);
} // makeFreqMap()
/** Creates a map of FFT frequency bins to comparison bins.
@@ -514,50 +604,53 @@
* is the energy is summed into the comparison bins. See also
* processFrame()
*/
- protected void makeStandardFrequencyMap(int fftSize, float sampleRate) {
+ protected void makeStandardFrequencyMap(int fftSize, float sampleRate,
+ double refFreq) {
double binWidth = sampleRate / fftSize;
int crossoverBin = (int)(2 / (Math.pow(2, 1/12.0) - 1));
- int crossoverMidi = (int)Math.round(Math.log(crossoverBin*binWidth/440)/
- Math.log(2) * 12 + 69);
- // freq = 440 * Math.pow(2, (midi-69)/12.0) / binWidth;
+ int crossoverMidi = (int)Math.round(Math.log(
+ crossoverBin * binWidth / refFreq) / Math.log(2) * 12 + 69);
+ // freq = refFreq * Math.pow(2, (midi-69)/12.0) / binWidth;
int i = 0;
while (i <= crossoverBin)
- freqMap[i++] = i;
+ freqMap[i++] = (int)Math.round(i*440/refFreq);
while (i <= fftSize/2) {
- double midi = Math.log(i*binWidth/440) / Math.log(2) * 12 + 69;
+ double midi = Math.log(i*binWidth/refFreq) / Math.log(2) * 12 + 69;
if (midi > 127)
midi = 127;
freqMap[i++] = crossoverBin + (int)Math.round(midi) - crossoverMidi;
}
freqMapSize = freqMap[i-1] + 1;
- // System.err.println("Map size: " + freqMapSize +
- // "; Crossover at: " + crossoverBin);
+ if (!silent) {
+ System.err.println("Map size: " + freqMapSize +
+ "; Crossover at: " + crossoverBin);
+ int stopAt = Math.min(freqMap.length, 500);
+ for (i = 0; i < stopAt; i++) // fftSize / 2; i++)
+ System.err.println("freqMap[" + i + "] = " + freqMap[i]);
+ System.err.println("Reference frequency: " + refFreq);
+ }
} // makeStandardFrequencyMap()
// Test whether chroma is better or worse
- protected void makeChromaFrequencyMap(int fftSize, float sampleRate) {
- double binWidth = sampleRate / fftSize;
+ protected void makeChromaFrequencyMap(int fftSize,float sR,double refFreq) {
+ double binWidth = sR / fftSize;
int crossoverBin = (int)(1 / (Math.pow(2, 1/12.0) - 1));
- int crossoverMidi = (int)Math.round(Math.log(crossoverBin*binWidth/440)/
- Math.log(2) * 12 + 69);
- // freq = 440 * Math.pow(2, (midi-69)/12.0) / binWidth;
+ // int crossoverMidi = (int)Math.round(Math.log(crossoverBin*binWidth/440)/
+ // Math.log(2) * 12 + 69);
+ // freq = refFreq * Math.pow(2, (midi-69)/12.0) / binWidth;
int i = 0;
while (i <= crossoverBin)
freqMap[i++] = 0;
while (i <= fftSize/2) {
- double midi = Math.log(i*binWidth/440) / Math.log(2) * 12 + 69;
+ double midi = Math.log(i*binWidth/refFreq) / Math.log(2) * 12 + 69;
freqMap[i++] = ((int)Math.round(midi)) % 12 + 1;
}
freqMapSize = 13;
- // System.err.println("Map size: " + freqMapSize +
- // "; Crossover at: " + crossoverBin);
- // for (i = 0; i < fftSize / 2; i++)
- // System.err.println("freqMap[" + i + "] = " + freqMap[i]);
} // makeChromaFrequencyMap()
/** Reads a frame of input data, averages the channels to mono, scales
* to a maximum possible absolute value of 1, and stores the audio data
- * in a circular input buffer.
+ * in a circular input buffer. Assumes 16 bit PCM, any number of channels.
* @return true if a frame (or part of a frame, if it is the final frame)
* is read. If a complete frame cannot be read, the InputStream is set
* to null.
@@ -621,9 +714,12 @@
return true;
} // getFrame()
+ Plot plot = null;
+ double[] plotX, plotY;
+
/** Processes a frame of audio data by first computing the STFT with a
- * Hamming window, then mapping the frequency bins into a part-linear
- * part-logarithmic array, then (optionally) computing the half-wave
+ * Hamming window, then scaling the frequency axis with an arbitrary
+ * mapping, then (optionally) computing the half-wave
* rectified spectral difference from the previous frame, then (optionally)
* normalising to a sum of 1, then calculating the distance to all frames
* stored in the otherMatcher and storing them in the distance matrix, and
@@ -661,13 +757,12 @@
bestPathCost[frameCount] = bpcOld;
bestPathCost[frameCount - blockSize] = bpcNew;
}
- double totalEnergy = 0;
+ double totalEnergy = 0; // for normalisation
if (useSpectralDifference) {
for (int i = 0; i < freqMapSize; i++) {
totalEnergy += newFrame[i];
if (newFrame[i] > prevFrame[i]) {
frames[frameIndex][i] = newFrame[i] - prevFrame[i];
-// totalEnergy += frames[frameIndex][i];
} else
frames[frameIndex][i] = 0;
}
@@ -677,21 +772,28 @@
totalEnergy += frames[frameIndex][i];
}
}
+ if (plot != null) {
+ if (plotX == null) {
+ plotX = new double[freqMapSize];
+ plotY = new double[freqMapSize];
+ for (int j=0; j < freqMapSize; j++)
+ plotX[j] = j;
+ plot.addPlot(plotX, plotY);
+ }
+ for (int i = 0; i < freqMapSize; i++)
+ plotY[i] = frames[frameIndex][i];
+ plot.update();
+ }
frames[frameIndex][freqMapSize] = totalEnergy;
if (wormHandler != null)
wormHandler.addPoint(totalEnergy);
-// System.err.print(Format.d(max(newFrame),3) + " " +
-// Format.d(totalEnergy,3));
double decay = frameCount >= 200? 0.99:
(frameCount < 100? 0: (frameCount - 100) / 100.0);
if (ltAverage == 0)
ltAverage = totalEnergy;
else
ltAverage = ltAverage * decay + totalEnergy * (1.0 - decay);
-// System.err.println(Format.d(ltAverage,4) + " " +
-// Format.d(totalEnergy) + " " +
-// Format.d(frameRMS));
- if (frameRMS <= silenceThreshold)
+ if (totalEnergy <= silenceThreshold) // was frameRMS
for (int i = 0; i < freqMapSize; i++)
frames[frameIndex][i] = 0;
else if (normalise1)
@@ -710,9 +812,13 @@
int mn=-1;
int mx=-1;
for ( ; index < stop; index++) {
+// tmp = stop - index;
int dMN = calcDistance(frames[frameIndex],
otherMatcher.frames[index % blockSize]);
- if (mx<0)
+ if (((ignore != null) && ignore[frameCount]) ||
+ ((otherMatcher.ignore != null) && otherMatcher.ignore[index]))
+ dMN /= 4;
+ if (mx < 0)
mx = mn = dMN;
else if (dMN > mx)
mx = dMN;
@@ -789,8 +895,6 @@
}
} // processFrame()
- // protected double mins = 100, maxs = -100;
-
/** Calculates the Manhattan distance between two vectors, with an optional
* normalisation by the combined values in the vectors. Since the
* vectors contain energy, this could be considered as a squared Euclidean
@@ -807,27 +911,32 @@
d += Math.abs(f1[i] - f2[i]);
sum += f1[i] + f2[i];
}
- // System.err.print(" " + Format.d(d,3));
if (sum == 0)
return 0;
if (normalise2)
return (int)(scale * d / sum); // 0 <= d/sum <= 2
if (!normalise4)
return (int)(scale * d);
- // double weight = (5 + Math.log(f1[freqMapSize] + f2[freqMapSize]))/10.0;
double weight = (8 + Math.log(sum)) / 10.0;
- // if (weight < mins) {
- // mins = weight;
- // System.err.println(Format.d(mins,3) + " " + Format.d(maxs));
- // }
- // if (weight > maxs) {
- // maxs = weight;
- // System.err.println(Format.d(mins,3) + " " + Format.d(maxs));
- // }
if (weight < 0)
weight = 0;
else if (weight > 1)
weight = 1;
+// if ((frameCount > 1000) && (tmp < 50) && (d/sum < 0.8)) {
+// double[] x = new double[f1.length];
+// for (int i=0; i < x.length; i++)
+// x[i] = i;
+// Plot p = new Plot();
+// p.addPlot(x, f1, Color.blue);
+// p.setLength(0, freqMapSize-2);
+// p.addPlot(x, f2, Color.red);
+// p.setLength(1, freqMapSize-2);
+// p.fitAxes();
+// p.setTitle(String.format("%5d %5d %5.3f %5.3f %5.3f %5.3f %5.3f\n",
+// frameCount, tmp, d, sum, weight, d/sum, d/sum*weight));
+// try{System.in.read();}catch(Exception e){}
+// p.close();
+// }
return (int)(scale * d / sum * weight);
} // calcDistance()
@@ -922,8 +1031,9 @@
for (Iterator i = events.iterator(); i.hasNext(); ) {
current = i.next();
if (count == 0) {
- sum = current.keyDown;
- if (hasMatchFile)
+ sum = current.keyDown; // will average "simultaneous" onsets
+ //if (hasMatchFile)
+ if ((metadata == MetaType.MATCH) || (metadata == MetaType.MIDI))
correction = matchFileOffset - current.keyDown;
count = 1;
prevBeat = current.scoreBeat;
@@ -951,14 +1061,16 @@
current = i.next();
if (count == 0) {
sum = current.keyDown;
- if (pm.hasMatchFile)
+ //if (pm.hasMatchFile)
+ if ((pm.metadata == MetaType.MATCH) ||
+ (pm.metadata == MetaType.MIDI))
correction = pm.matchFileOffset - current.keyDown;
count = 1;
prevBeat = current.scoreBeat;
} else if (current.scoreBeat == prevBeat) {
sum += current.keyDown;
count++;
- } else {
+ } else { // insert in list sorted (keyed) by beat number
while ((onset.beat < prevBeat) && li.hasNext())
onset = li.next();
while ((onset.beat > prevBeat) && li.hasPrevious())
@@ -973,7 +1085,7 @@
prevBeat = current.scoreBeat;
}
}
- if (count != 0) {
+ if (count != 0) { // insert last event
while ((onset.beat < prevBeat) && li.hasNext())
onset = li.next();
while ((onset.beat > prevBeat) && li.hasPrevious())
@@ -989,7 +1101,7 @@
onset = li.next();
String s = String.format("%8.3f %8.3f %8.3f",
onset.beat, onset.time1, onset.time2);
- if ((onset.time1 < 0) || (onset.time2 < 0)) {
+ if ((onset.time1 < 0) || (onset.time2 < 0) || (onset.beat < 0)) {
System.err.println("Match Error: " + s);
li.remove(); // notes must exist in both performances
}
@@ -1075,12 +1187,15 @@
* -m2 matchFile2 offset2 matchFile + start time for inputFile2
* -w1 wormFile1 wormFile for inputFile1
* -w2 wormFile2 wormFile for inputFile2
- * -n1 normalise FFT frames before comparison
- * -N1 do not normalise FFT frames before comparison (default)
- * -n2 normalise distance metric
- * -N2 do not normalise distance metric (default)
- * -n3 normalise .........
- * -N3 do not normalise (default)
+ * -[nN]k Various options for normalisation, where
+ * n switches on, N switches off each option.
+ * k can have the following values:
+ * - 1: normalise each FFT frame (sum of energy = 1) before comparison (default=true)
+ *
- 2: normalise distance metric by sum of energies of both frames (default=false)
+ *
- 3: normalise each FFT frame by the medium-term average energy (default=false)
+ *
- 4: normalise distance metric by thresholded log of sum of frames (default=true)
+ *
- 5: set distance to zero for non-annotated positions in either file (default=false)
+ *
* -d use half-wave rectified spectral difference (default)
* -D do not use half-wave rectified spectral difference
* -s scale set scaling factor for distance metric
@@ -1095,6 +1210,11 @@
* -W live worm output (file 2)
* -z fileName worm output (map file 1 to 2)
* -Z fileName worm output (map file 2 to 1)
+ * -ob fileName output backward match path
+ * -of fileName output forward match path
+ * -os fileName output smoothed match path
+ * -rf1 value set A4 reference frequency (default 440) for pm1
+ * -rf2 value set A4 reference frequency for pm2
*
*/
public static int processArgs(PerformanceMatcher pm1,
@@ -1148,6 +1268,12 @@
pm1.setMatchFile(args[++i], 0, true);
} else if (args[i].equals("-w2")) {
pm2.setMatchFile(args[++i], 0, true);
+ } else if (args[i].equals("-M1")) {
+ pm1.setLabelFile(args[++i]);
+ MatrixFrame.useSmoothPath = false;
+ } else if (args[i].equals("-M2")) {
+ pm2.setLabelFile(args[++i]);
+ MatrixFrame.useSmoothPath = false;
} else if (args[i].equals("-d")) {
pm1.useSpectralDifference = pm2.useSpectralDifference = true;
} else if (args[i].equals("-D")) {
@@ -1168,6 +1294,16 @@
pm1.normalise4 = pm2.normalise4 = true;
} else if (args[i].equals("-N4")) {
pm1.normalise4 = pm2.normalise4 = false;
+ } else if (args[i].equals("-k1")) {
+ pm1.normalise5 = true;
+ pm2.normalise5 = false;
+ } else if (args[i].equals("-k2")) {
+ pm1.normalise5 = false;
+ pm2.normalise5 = true;
+ } else if (args[i].equals("--plot1")) {
+ pm1.plot = new Plot();
+ } else if (args[i].equals("--plot2")) {
+ pm2.plot = new Plot();
} else if (args[i].equals("--use-chroma-map")) {
pm1.useChromaFrequencyMap = pm2.useChromaFrequencyMap = true;
} else if (args[i].equals("-b")) {
@@ -1215,13 +1351,30 @@
pm2.wormHandler = new WormHandler(pm2);
pm2.liveWorm = true;
} else if (args[i].equals("-z")) {
- batchMode = true;
- pm1.wormHandler = new WormHandler(pm1);
+ //batchMode = true; //TODO check if scripts are broken
+ //if (!pm2.hasLabelFile) //TODO why !hLabel instead of hWorm
+ if (pm2.metadata == MetaType.WORM)
+ pm1.wormHandler = new WormHandler(pm1);
pm1.matchFileName = args[++i];
} else if (args[i].equals("-Z")) {
- batchMode = true;
- pm2.wormHandler = new WormHandler(pm2);
+ //batchMode = true; //TODO check if scripts are broken
+ //if (!pm1.hasLabelFile)
+ if (pm1.metadata == MetaType.WORM)
+ pm2.wormHandler = new WormHandler(pm2);
pm2.matchFileName = args[++i];
+ } else if (args[i].equals("-ob")) {
+ pm1.outputFileName = args[++i];
+ pm1.outputType = PathType.BACKWARD;
+ } else if (args[i].equals("-of")) {
+ pm1.outputFileName = args[++i];
+ pm1.outputType = PathType.FORWARD;
+ } else if (args[i].equals("-os")) {
+ pm1.outputFileName = args[++i];
+ pm1.outputType = PathType.SMOOTHED;
+ } else if (args[i].equals("-rf1")) {
+ pm1.referenceFrequency = Double.parseDouble(args[++i]);
+ } else if (args[i].equals("-rf2")) {
+ pm2.referenceFrequency = Double.parseDouble(args[++i]);
} else
return i;
// System.err.println("WARNING: Ignoring argument: " + args[i]);
@@ -1252,14 +1405,37 @@
if (pm2.pcmInputStream != null) {
doMatch(pm1, pm2, s);
s.updatePaths(false);
- if ((pm1.hasMatchFile && pm2.hasMatchFile) ||
- (pm1.hasWormFile && pm2.hasWormFile))
+ //if ((pm1.hasMatchFile && pm2.hasMatchFile) ||
+ // (pm1.hasWormFile && pm2.hasWormFile))
+ if ((pm1.metadata == pm2.metadata) &&
+ ((pm1.metadata == MetaType.MATCH) ||
+ (pm1.metadata == MetaType.WORM)))
s.evaluatePaths();
- else if (pm1.hasWormFile && !pm2.hasWormFile) {
- if ((pm2.matchFileName != null) && !pm2.hasMatchFile)
+ //else if (pm1.hasWormFile && !pm2.hasWormFile) {
+ else if ((pm1.metadata != MetaType.NONE) &&
+ (pm2.metadata == MetaType.NONE)) {
+ //if (pm1.hasLabelFile)
+ if (pm1.metadata == MetaType.LABEL)
+ pm2.writeLabelFile(s);
+ else if (pm1.metadata == MetaType.MIDI)
+ pm2.writeMidiFile(s);
+ //else if ((pm2.matchFileName != null) && !pm2.hasMatchFile)
+ else if (pm2.matchFileName != null)
s.wormHandler.write(new File(pm2.matchFileName), false);
else
g.saveWormFile();
+ } else if (pm1.outputFileName != null) {
+ switch (pm1.outputType) {
+ case BACKWARD:
+ s.saveBackwardPath(pm1.outputFileName);
+ break;
+ case FORWARD:
+ s.saveForwardPath(pm1.outputFileName);
+ break;
+ case SMOOTHED:
+ s.saveSmoothedPath(pm1.outputFileName);
+ break;
+ }
}
}
if (!silent)
diff -r 9feddf959b6b -r 88f50ba37174 at/ofai/music/match/ScrollingMatrix.java
--- a/at/ofai/music/match/ScrollingMatrix.java Fri Oct 08 16:02:41 2010 +0100
+++ b/at/ofai/music/match/ScrollingMatrix.java Fri Oct 08 16:03:47 2010 +0100
@@ -528,6 +528,7 @@
sPathLength = Path.smooth(sPathX, sPathY, bPathLength);
if ((pm1.audioFile == null) || (pm2.audioFile == null))
return;
+ //if (MatrixFrame.useSmoothPath) {
if (pm1.audioFile.isReference) {
pm2.audioFile.setMatch(sPathX, pm2.hopTime, sPathY, pm1.hopTime,
sPathLength);
@@ -538,8 +539,31 @@
pm2.audioFile.setFixedPoints(lastPoint, false);
System.err.println("Warning: pm1 is not reference file");
}
+ //}
} // updateSmoothedPath()
+ public void saveBackwardPath(String fileName) {
+ savePath(fileName, bPathX, bPathY, bPathLength);
+ } // saveBackwardPath()
+
+ public void saveForwardPath(String fileName) {
+ savePath(fileName, fPathX, fPathY, fPathLength);
+ } // saveForwardPath()
+
+ public void saveSmoothedPath(String fileName) {
+ savePath(fileName, sPathX, sPathY, sPathLength);
+ } // saveSmoothedPath()
+
+ protected void savePath(String fileName, int[] pathX, int[] pathY, int len){
+ try {
+ java.io.PrintStream out = new java.io.PrintStream(fileName);
+ for (int i = len-1; i >= 0; i--)
+ out.printf(" %7.3f %7.3f\n", hop1 * pathY[i], hop2 * pathX[i]);
+ } catch (Exception e) {
+ System.err.println("Error saving path: " + e);
+ }
+ } // savePath()
+
public int[] forwardPathX() {
return replace(fPathX, fPathLength, fPathLength);
} // forwardPathX()