Mercurial > hg > sonic-lineup
changeset 423:93d4b4a98305
Merge from branch pitch-align, with the groundwork for pitch-based alignment...
| author | Chris Cannam |
|---|---|
| date | Wed, 13 May 2020 14:11:43 +0100 |
| parents | 011d39dc7350 (current diff) eb4a9dca0acd (diff) |
| children | d9b544de4c8a |
| files | |
| diffstat | 13 files changed, 345 insertions(+), 40 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed Apr 01 11:03:02 2020 +0100 +++ b/.hgignore Wed May 13 14:11:43 2020 +0100 @@ -61,3 +61,4 @@ qm-vamp-plugins ..* deploy/linux/docker/output +*.AppImage
--- a/main/MainWindow.cpp Wed Apr 01 11:03:02 2020 +0100 +++ b/main/MainWindow.cpp Wed May 13 14:11:43 2020 +0100 @@ -58,7 +58,6 @@ #include "audio/AudioCallbackRecordTarget.h" #include "audio/PlaySpeedRangeMapper.h" #include "data/fileio/DataFileReaderFactory.h" -#include "data/fileio/PlaylistFileReader.h" #include "data/fileio/WavFileWriter.h" #include "data/fileio/CSVFileWriter.h" #include "data/fileio/BZipFileDevice.h" @@ -391,16 +390,7 @@ NetworkPermissionTester tester; m_networkPermission = tester.havePermission(); - - if (!reopenLastSession()) { - QTimer::singleShot(400, this, SLOT(introDialog())); - } else { - // Do this here only if not showing the intro dialog - - // otherwise the introDialog function will do this after it - // has shown the dialog, so we don't end up with both at once - checkForNewerVersion(); - } - + // QTimer::singleShot(500, this, SLOT(betaReleaseWarning())); } @@ -2488,17 +2478,6 @@ } void -MainWindow::audioTimeStretchMultiChannelDisabled() -{ - static bool shownOnce = false; - if (shownOnce) return; - QMessageBox::information - (this, tr("Audio processing overload"), - tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload.")); - shownOnce = true; -} - -void MainWindow::introDialog() { IntroDialog::show(this); @@ -2906,7 +2885,7 @@ } void -MainWindow::alignmentFailed(QString message) +MainWindow::alignmentFailed(ModelId, QString message) { QMessageBox::warning (this,
--- a/main/MainWindow.h Wed Apr 01 11:03:02 2020 +0100 +++ b/main/MainWindow.h Wed May 13 14:11:43 2020 +0100 @@ -75,6 +75,7 @@ public slots: void openSmallSession(const SmallSession &); + bool reopenLastSession(); void preferenceChanged(PropertyContainer::PropertyName) override; bool commitData(bool mayAskUser); // on session shutdown @@ -84,6 +85,9 @@ void selectMainPane(); + void introDialog(); + void checkForNewerVersion(); + protected slots: virtual void openFiles(); virtual void openLocation(); @@ -93,7 +97,6 @@ virtual void newSession(); virtual void preferences(); - bool reopenLastSession(); void closeSession() override; void outlineWaveformModeSelected(); @@ -129,15 +132,11 @@ virtual void restoreNormalPlayback(); void monitoringLevelsChanged(float, float) override; - - void introDialog(); - void checkForNewerVersion(); void betaReleaseWarning(); void sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool) override; void audioOverloadPluginDisabled() override; - void audioTimeStretchMultiChannelDisabled() override; void documentModified() override; void documentRestored() override; @@ -161,7 +160,7 @@ void modelRegenerationWarning(QString, QString, QString) override; void alignmentComplete(ModelId) override; - void alignmentFailed(QString) override; + void alignmentFailed(ModelId, QString) override; virtual void salientLayerCompletionChanged(ModelId);
--- a/main/main.cpp Wed Apr 01 11:03:02 2020 +0100 +++ b/main/main.cpp Wed May 13 14:11:43 2020 +0100 @@ -19,6 +19,7 @@ #include "base/TempDirectory.h" #include "base/PropertyContainer.h" #include "base/Preferences.h" +#include "data/fileio/PlaylistFileReader.h" #include "widgets/TipDialog.h" #include "svcore/plugin/PluginScan.h" @@ -302,16 +303,57 @@ SmallSession session; bool haveSession = false; + + QStringList filePaths; for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { if (i == args.begin()) continue; if (i->startsWith('-')) continue; + QString arg = *i; + + // If an arg is a playlist file, we can streamline things and + // make sure we get the proper absolute paths by expanding it + // here, rather than adding it to the session and waiting for + // it to be expanded in the main application logic. (That + // would work too, it's just not so clean a user experience.) + + if (PlaylistFileReader::isSupported(arg)) { + PlaylistFileReader reader(arg); + if (!reader.isOK()) { + // But if we can't open the playlist file, add it to + // the session as if it were just any old file and let + // the main application worry about it later - we + // don't want to be popping up dialogs before the app + // has been exec'd + filePaths.push_back(arg); + } else { + auto playlist = reader.load(); + for (auto entry: playlist) { + filePaths.push_back(entry); + } + } + } else { + filePaths.push_back(arg); + } + } + + for (auto filePath: filePaths) { + + // Add the argument to our session as a file path or URL to be + // opened. We want to avoid relative file paths, but to do so + // we must first check that they are not absolute URLs. + + QUrl url(filePath); + if (url.isRelative()) { + filePath = QFileInfo(filePath).absoluteFilePath(); + } + if (session.mainFile == "") { - session.mainFile = *i; + session.mainFile = filePath; } else { - session.additionalFiles.push_back(*i); + session.additionalFiles.push_back(filePath); } haveSession = true; @@ -319,6 +361,13 @@ if (haveSession) { gui->openSmallSession(session); + } else if (!gui->reopenLastSession()) { + QTimer::singleShot(400, gui, SLOT(introDialog())); + } else { + // Do this here only if not showing the intro dialog - + // otherwise the introDialog function will do this after it + // has shown the dialog, so we don't end up with both at once + gui->checkForNewerVersion(); } int rv = application.exec();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/Makefile Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,13 @@ + +SCRIPTS := ../../sml-buildscripts + +pitch-track-align: pitch-track-align.mlb pitch-track-align.deps + mlton pitch-track-align.mlb + +pitch-track-align.deps: pitch-track-align.mlb + ${SCRIPTS}/mlb-dependencies $^ > $@ + +clean: + rm -f pitch-track-align *.deps + +-include *.deps
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/main.sml Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,1 @@ +val _ = main ()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/note-track.ttl Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,7 @@ +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix vamp: <http://purl.org/ontology/vamp/> . +@prefix : <#> . + +:transform a vamp:Transform ; + vamp:plugin <http://vamp-plugins.org/rdf/plugins/pyin#pyin> ; + vamp:output <http://vamp-plugins.org/rdf/plugins/pyin#pyin_output_notes> .
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/pitch-track-align.mlb Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,3 @@ +$(SML_LIB)/basis/basis.mlb +pitch-track-align.sml +main.sml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/pitch-track-align.sh Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,32 @@ +#!/bin/bash + +mydir=$(dirname "$0") + +set -eu + +file1="$1" +file2="$2" + +export PATH=$PATH:"$mydir"/../../sonic-annotator + +tmproot=/tmp/pitch-track-align-"$$" +trap "rm -f $tmproot.a $tmproot.b" 0 + +#sonic-annotator -t "$mydir/pitch-track.ttl" "$file1" -w csv --csv-one-file "$tmproot.a" --csv-omit-filename --csv-force +#sonic-annotator -t "$mydir/pitch-track.ttl" "$file2" -w csv --csv-one-file "$tmproot.b" --csv-omit-filename --csv-force + +sonic-annotator -t "$mydir/note-track.ttl" "$file1" -w csv --csv-omit-filename --csv-stdout | awk -F, '{ print $1 "," $3 }' > "$tmproot.a" +sonic-annotator -t "$mydir/note-track.ttl" "$file2" -w csv --csv-omit-filename --csv-stdout | awk -F, '{ print $1 "," $3 }' > "$tmproot.b" + +echo 1>&2 +echo "First track:" 1>&2 +cat "$tmproot.a" 1>&2 + +echo 1>&2 +echo "Second track:" 1>&2 +cat "$tmproot.b" 1>&2 + +echo "0,0" + +"$mydir"/pitch-track-align "$tmproot.a" "$tmproot.b" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/pitch-track-align.sml Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,209 @@ + +datatype pitch_direction = + PITCH_NONE | + PITCH_UP of real | + PITCH_DOWN of real + +type value = pitch_direction +type cost = real + +fun choose costs = + case costs of + (NONE, NONE, _) => 0.0 + | (SOME a, NONE, _) => a + | (NONE, SOME b, _) => b + | (SOME _, SOME _, NONE) => raise Fail "Internal error" + | (SOME a, SOME b, SOME both) => + if a < b then + if both <= a then both else a + else + if both <= b then both else b + +fun cost (p1, p2) = + let fun together a b = let val diff = Real.abs (a - b) in + if diff < 1.0 then ~1.0 + else if diff > 3.0 then 1.0 + else 0.0 + end + fun opposing a b = let val diff = a + b in + if diff < 2.0 then 1.0 + else 2.0 + end + in + case (p1, p2) of + (PITCH_NONE, PITCH_NONE) => 0.0 + | (PITCH_UP a, PITCH_UP b) => together a b + | (PITCH_UP a, PITCH_DOWN b) => opposing a b + | (PITCH_DOWN a, PITCH_UP b) => opposing a b + | (PITCH_DOWN a, PITCH_DOWN b) => together a b + | _ => 1.0 + end + +fun costSeries (s1 : value vector) (s2 : value vector) : cost vector vector = + let open Vector + + fun costSeries' (rowAcc : cost vector list) j = + if j = length s1 + then fromList (rev rowAcc) + else costSeries' (costRow' rowAcc j [] 0 :: rowAcc) (j+1) + + and costRow' (rowAcc : cost vector list) j (colAcc : cost list) i = + if i = length s2 + then fromList (rev colAcc) + else let val c = cost (sub (s1, j), sub (s2, i)) + val options = + (if null rowAcc + then NONE + else SOME (c + sub (hd rowAcc, i)), + if i = 0 + then NONE + else SOME (c + hd colAcc), + if null rowAcc orelse i = 0 + then NONE + else SOME (c + sub (hd rowAcc, i-1))) + in + costRow' rowAcc j (choose options :: colAcc) (i+1) + end + in + costSeries' [] 0 + end + +fun alignSeries s1 s2 = + let val cumulativeCosts = costSeries s1 s2 + fun cost (j, i) = Vector.sub (Vector.sub (cumulativeCosts, j), i) + fun trace (j, i) acc = + if i = 0 + then if j = 0 + then i :: acc + else trace (j-1, i) (i :: acc) + else if j = 0 + then trace (j, i-1) acc + else let val (a, b, both) = + (cost (j-1, i), cost (j, i-1), cost (j-1, i-1)) + in + if a < b then + if both <= a + then trace (j-1, i-1) (i :: acc) + else trace (j-1, i) (i :: acc) + else + if both <= b + then trace (j-1, i-1) (i :: acc) + else trace (j, i-1) acc + end + + val sj = Vector.length s1 + val si = Vector.length s2 + in + Vector.fromList + (if si = 0 orelse sj = 0 + then [] + else trace (sj-1, si-1) []) + end + +fun preprocess (times : real list, frequencies : real list) : + real vector * value vector * real vector = + let val pitches = + map (fn f => + if f < 0.0 + then 0.0 + else Real.realRound (12.0 * (Math.log10(f / 220.0) / + Math.log10(2.0)) + 57.0)) + frequencies + val values = + let val acc = + foldl (fn (p, (acc, prev)) => + if p <= 0.0 then (PITCH_NONE :: acc, prev) + else if prev <= 0.0 + then (PITCH_UP 0.0 :: acc, p) + else if p >= prev + then (PITCH_UP (p - prev) :: acc, p) + else (PITCH_DOWN (prev - p) :: acc, p)) + ([], 0.0) + pitches + in + rev (#1 acc) + end + val _ = + app (fn (text, p) => + TextIO.output (TextIO.stdErr, ("[" ^ text ^ "] -> " ^ + Real.toString p ^ "\n"))) + (ListPair.map (fn (PITCH_NONE, p) => (" ", p) + | (PITCH_UP d, p) => ("+", p) + | (PITCH_DOWN d, p) => ("-", p)) + (values, pitches)) + in + (Vector.fromList times, + Vector.fromList values, + Vector.fromList pitches) + end + +fun read csvFile = + let fun toNumberPair line = + case String.fields (fn c => c = #",") line of + a::b::_ => (case (Real.fromString a, Real.fromString b) of + (SOME r1, SOME r2) => (r1, r2) + | _ => raise Fail ("Failed to parse numbers: " ^ + line)) + | _ => raise Fail ("Not enough columns: " ^ line) + fun read' s acc = + case TextIO.inputLine s of + SOME line => + let val pair = toNumberPair + (String.substring + (line, 0, String.size line - 1)) + in + read' s (pair :: acc) + end + | NONE => rev acc + val stream = TextIO.openIn csvFile + val (timeList, freqList) = ListPair.unzip (read' stream []) + val _ = TextIO.closeIn stream + in + preprocess (timeList, freqList) + end + +fun meanDiff pitches1 pitches2 mapping = + let open Vector + val n = length mapping + val sumDiff = + foldli (fn (i, j, acc) => acc + + sub (pitches1, i) - + sub (pitches2, j)) + 0.0 mapping + in + if n = 0 then 0.0 + else sumDiff / Real.fromInt n + end + +fun alignFiles csv1 csv2 = + let val (times1, values1, pitches1) = read csv1 + val (times2, values2, pitches2) = read csv2 + (* raw alignment returns the index into pitches2 for each + element in pitches1 *) + val raw = alignSeries values1 values2 + val _ = TextIO.output (TextIO.stdErr, + "Mean pitch difference: reference " ^ + Real.toString (meanDiff pitches1 pitches2 raw) + ^ " semitones higher than other track\n") + in + List.tabulate (Vector.length raw, + fn i => (Vector.sub (times1, i), + Vector.sub (times2, Vector.sub (raw, i)))) + end + +fun printAlignment alignment = + app (fn (from, to) => + print (Real.toString from ^ "," ^ Real.toString to ^ "\n")) + alignment + +fun usage () = + TextIO.output (TextIO.stdErr, + "Usage: pitch-track-align pitch1.csv pitch2.csv\n") + +fun main () = + (case CommandLine.arguments () of + [csv1, csv2] => printAlignment (alignFiles csv1 csv2) + | _ => usage ()) + handle exn => + (TextIO.output (TextIO.stdErr, "Error: " ^ (exnMessage exn) ^ "\n"); + OS.Process.exit OS.Process.failure)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pitch-track-align/pitch-track.ttl Wed May 13 14:11:43 2020 +0100 @@ -0,0 +1,11 @@ +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix vamp: <http://purl.org/ontology/vamp/> . +@prefix : <#> . + +:transform a vamp:Transform ; + vamp:plugin <http://vamp-plugins.org/rdf/plugins/pyin#pyin> ; + vamp:parameter_binding [ + vamp:parameter [ vamp:identifier "outputunvoiced" ] ; + vamp:value "2"^^xsd:float ; + ] ; + vamp:output <http://vamp-plugins.org/rdf/plugins/pyin#pyin_output_smoothedpitchtrack> .
--- a/repoint-lock.json Wed Apr 01 11:03:02 2020 +0100 +++ b/repoint-lock.json Wed May 13 14:11:43 2020 +0100 @@ -1,25 +1,25 @@ { "libraries": { "vamp-plugin-sdk": { - "pin": "74c5b0bfa108" + "pin": "8ffb8985ae8f" }, "svcore": { - "pin": "498ed1e86f92" + "pin": "f36fef97ac81" }, "svgui": { - "pin": "27ea5d61b402" + "pin": "52d4bfba5b3d" }, "svapp": { - "pin": "7b1d30af4b38" + "pin": "6429a164b7e1" }, "checker": { - "pin": "ef64b3f171d9" + "pin": "e839338d3869" }, "piper": { - "pin": "f5a04ffe4d5a0ae01e77018a86a59b48a425e674" + "pin": "3a742c556ac1f2bf9823f30b937c71c690e1f6ae" }, "piper-vamp-cpp": { - "pin": "f381235a4ba88eac2fa31fc1f7f613cca9707f63" + "pin": "f0d3ab2952b21d287b481759bda986427df10ef7" }, "dataquay": { "pin": "35098262cadd" @@ -61,7 +61,7 @@ "pin": "a7d9c6142f8f" }, "nnls-chroma": { - "pin": "1efe67570b75" + "pin": "82d5d11b68d7" }, "qm-vamp-plugins": { "pin": "06933fecfa33aec41a07cc865098be7545ffcca3"
