Mercurial > hg > match
changeset 1:88f50ba37174 tip
* Import MATCH v0.9.4
author | Chris Cannam |
---|---|
date | Fri, 08 Oct 2010 16:03:47 +0100 |
parents | 9feddf959b6b |
children | |
files | at/ofai/music/match/AudioFile.java at/ofai/music/match/GUI.java at/ofai/music/match/MatrixFrame.java at/ofai/music/match/PerformanceMatcher.java at/ofai/music/match/ScrollingMatrix.java |
diffstat | 5 files changed, 351 insertions(+), 106 deletions(-) [+] |
line wrap: on
line diff
--- 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;
--- 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;
--- 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);
--- 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 - * <code>first</code>) */ + * <code>firstPM</code>) */ 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 <b>-n1</b> and <b>-N1</b>) 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 <b>-n2</b> and <b>-N2</b>) indicating whether @@ -105,9 +116,15 @@ /** Flag (command line options <b>-n4</b> and <b>-N4</b>) 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 <b>-n5</b> and <b>-N5</b>) 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 <b>-d</b> and <b>-D</b>) 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 * <b>-f FFTTime</b>. (Default = 0.04644s). Note that the value is not * taken to be precise; it is adjusted so that <code>fftSize</code> 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 <code>pcmInputStream</code> 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<Event> 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 @@ * <li><b>-m2 matchFile2 offset2</b> matchFile + start time for inputFile2 * <li><b>-w1 wormFile1</b> wormFile for inputFile1 * <li><b>-w2 wormFile2</b> wormFile for inputFile2 - * <li><b>-n1</b> normalise FFT frames before comparison - * <li><b>-N1</b> do not normalise FFT frames before comparison (default) - * <li><b>-n2</b> normalise distance metric - * <li><b>-N2</b> do not normalise distance metric (default) - * <li><b>-n3</b> normalise ......... - * <li><b>-N3</b> do not normalise (default) + * <li><b>-[nN]<i>k</i></b> Various options for normalisation, where + * <b>n</b> switches on, <b>N</b> switches off each option. + * <b><i>k</i></b> can have the following values:<ul> + * <li>1: normalise each FFT frame (sum of energy = 1) before comparison (default=true) + * <li>2: normalise distance metric by sum of energies of both frames (default=false) + * <li>3: normalise each FFT frame by the medium-term average energy (default=false) + * <li>4: normalise distance metric by thresholded log of sum of frames (default=true) + * <li>5: set distance to zero for non-annotated positions in either file (default=false) + * </ul> * <li><b>-d</b> use half-wave rectified spectral difference (default) * <li><b>-D</b> do not use half-wave rectified spectral difference * <li><b>-s scale</b> set scaling factor for distance metric @@ -1095,6 +1210,11 @@ * <li><b>-W</b> live worm output (file 2) * <li><b>-z fileName</b> worm output (map file 1 to 2) * <li><b>-Z fileName</b> worm output (map file 2 to 1) + * <li><b>-ob fileName</b> output backward match path + * <li><b>-of fileName</b> output forward match path + * <li><b>-os fileName</b> output smoothed match path + * <li><b>-rf1 value</b> set A4 reference frequency (default 440) for pm1 + * <li><b>-rf2 value</b> set A4 reference frequency for pm2 * </ul> */ 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)
--- 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()