Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix diffsinger pitch: pre&tail SP pitch #1319

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 73 additions & 64 deletions OpenUtau.Core/Editing/NoteBatchEdits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
foreach (var note in notes) {
if (note.lyric != lyric && (note.Next == null || note.Next.position > note.End + 120)) {
var addNote = project.CreateNote(note.tone, note.End, 120);
foreach(var exp in note.phonemeExpressions.OrderBy(exp => exp.index)) {
foreach (var exp in note.phonemeExpressions.OrderBy(exp => exp.index)) {
addNote.SetExpression(project, project.tracks[part.trackNo], exp.abbr, new float?[] { exp.value });
}
toAdd.Add(addNote);
Expand Down Expand Up @@ -52,7 +52,7 @@ public RemoveTailNote(string lyric, string name) {
}

bool NeedToBeRemoved(UNote note) {
return note.lyric == lyric
return note.lyric == lyric
&& (note.Next == null || note.Next.position > note.End);
}

Expand Down Expand Up @@ -124,7 +124,7 @@ public class Transpose : BatchEdit {

public Transpose(int deltaNoteNum, string name) {
this.deltaNoteNum = deltaNoteNum;
this.name= name;
this.name = name;
}

public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, DocManager docManager) {
Expand Down Expand Up @@ -187,7 +187,7 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
}
}

public class FixOverlap: BatchEdit {
public class FixOverlap : BatchEdit {
/// <summary>
/// Fix overlapping notes.
/// If multiple notes start at the same time, only the one with the highest tone will be kept
Expand All @@ -203,23 +203,23 @@ public FixOverlap() {

public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, DocManager docManager) {
var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList();
if(notes.Count == 0){
if (notes.Count == 0) {
return;
}
docManager.StartUndoGroup();
var currentNote = notes[0];
foreach(var note in notes.Skip(1)){
if(note.position == currentNote.position){
if(note.tone > currentNote.tone){
foreach (var note in notes.Skip(1)) {
if (note.position == currentNote.position) {
if (note.tone > currentNote.tone) {
docManager.ExecuteCmd(new RemoveNoteCommand(part, currentNote));
currentNote = note;
}else{
} else {
docManager.ExecuteCmd(new RemoveNoteCommand(part, note));
}
}else if(note.position < currentNote.End){
} else if (note.position < currentNote.End) {
docManager.ExecuteCmd(new ResizeNoteCommand(part, currentNote, note.position - currentNote.End));
currentNote = note;
}else{
} else {
currentNote = note;
}
}
Expand All @@ -235,11 +235,11 @@ public class HanziToPinyin : BatchEdit {
public HanziToPinyin() {
name = "pianoroll.menu.notes.hanzitopinyin";
}

public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, DocManager docManager) {
var pinyinResult = BaseChinesePhonemizer.Romanize(selectedNotes.Select(note=>note.lyric));
var pinyinResult = BaseChinesePhonemizer.Romanize(selectedNotes.Select(note => note.lyric));
docManager.StartUndoGroup(true);
foreach(var t in Enumerable.Zip(selectedNotes, pinyinResult,
foreach (var t in Enumerable.Zip(selectedNotes, pinyinResult,
(note, pinyin) => Tuple.Create(note, pinyin))) {
docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, t.Item1, t.Item2));
}
Expand Down Expand Up @@ -276,7 +276,7 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
if (maxPreutter > prevDur * 0.9f) {
maxPreutter = prevDur * 0.9f;
}
if(maxPreutter > phoneme.preutter) {
if (maxPreutter > phoneme.preutter) {
docManager.ExecuteCmd(new PhonemePreutterCommand(part, note, phoneme.index, (float)(maxPreutter - phoneme.autoPreutter)));
preutter = maxPreutter;
}
Expand Down Expand Up @@ -328,7 +328,8 @@ public void RunAsync(
int finished = 0;
setProgressCallback(0, phrases.Length);
var commands = new List<SetCurveCommand>();
foreach (var phrase in phrases) {
for (int ph_i = phrases.Count() - 1; ph_i >= 0; ph_i--) {
var phrase = phrases[ph_i];
var result = renderer.LoadRenderedPitch(phrase);
if (result == null) {
continue;
Expand All @@ -337,11 +338,20 @@ public void RunAsync(
int? lastY = null;
// TODO: Optimize interpolation and command.
if (cancellationToken.IsCancellationRequested) break;
// Take the first negative tick before start and the first tick after end for each segment;
// Reverse traversal, so that when the score slices are too close, priority is given to covering the consonant pitch of the next segment, reducing the impact on vowels.
for (int i = 0; i < result.tones.Length; i++) {
if (result.tones[i] < 0) {
continue;
}
int x = phrase.position - part.position + (int)result.ticks[i];
if (result.ticks[i] < 0) {
if (i + 1 < result.ticks.Length && result.ticks[i + 1] > 0) { } else
continue;
}
if (x >= phrase.position + phrase.duration) {
i = result.tones.Length - 1;
}
int pitchIndex = Math.Clamp((x - (phrase.position - part.position - phrase.leading)) / 5, 0, phrase.pitches.Length - 1);
float basePitch = phrase.pitchesBeforeDeviation[pitchIndex];
int y = (int)(result.tones[i] * 100 - basePitch);
Expand All @@ -366,14 +376,14 @@ public void RunAsync(
}
}

public class BakePitch: BatchEdit {
public class BakePitch : BatchEdit {
public virtual string Name => name;
private string name;
public BakePitch() {
name = "pianoroll.menu.notes.bakepitch";
}

struct Point{
struct Point {
public int X;
public double Y;
public PitchPointShape shape;
Expand All @@ -388,19 +398,19 @@ public Point ChangeShape(PitchPointShape shape) {
}
}

double deltaY(Point pt, Point lineStart, Point lineEnd, PitchPointShape shape){
double deltaY(Point pt, Point lineStart, Point lineEnd, PitchPointShape shape) {
return pt.Y - MusicMath.InterpolateShape(lineStart.X, lineEnd.X, lineStart.Y, lineEnd.Y, pt.X, shape);
}

PitchPointShape DetermineShape(Point start, Point middle, Point end){
if(start.Y==end.Y){
PitchPointShape DetermineShape(Point start, Point middle, Point end) {
if (start.Y == end.Y) {
return PitchPointShape.l;
}
var k = (middle.Y-start.Y)/(end.Y-start.Y);
if(k > 0.67){
var k = (middle.Y - start.Y) / (end.Y - start.Y);
if (k > 0.67) {
return PitchPointShape.o;
}
if(k < 0.33){
if (k < 0.33) {
return PitchPointShape.i;
}
return PitchPointShape.l;
Expand All @@ -420,13 +430,13 @@ List<Point> simplifyShape(List<Point> pointList, Double epsilon) {
if (pointList.Count <= 2) {
return pointList;
}

// Determine line shape
var middlePoint = pointList[pointList.Count / 2];
var startPoint = pointList[0];
var endPoint = pointList[^1];
var shape = DetermineShape(startPoint, middlePoint, endPoint);

// Find the point with the maximum distance from line between start and end
var dmax = 0.0;
var index = 0;
Expand Down Expand Up @@ -458,8 +468,7 @@ List<Point> simplifyShape(List<Point> pointList, Double epsilon) {
return results;
}

public static int LastIndexOfMin<T>(IList<T> self, Func<T, double> selector, int startIndex, int endIndex)
{
public static int LastIndexOfMin<T>(IList<T> self, Func<T, double> selector, int startIndex, int endIndex) {
if (self == null) {
throw new ArgumentNullException("self");
}
Expand All @@ -468,7 +477,7 @@ public static int LastIndexOfMin<T>(IList<T> self, Func<T, double> selector, int
throw new ArgumentException("List is empty.", "self");
}

var min = selector(self[endIndex-1]);
var min = selector(self[endIndex - 1]);
int minIndex = endIndex - 1;

for (int i = endIndex - 1; i >= startIndex; --i) {
Expand All @@ -495,7 +504,7 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
//Dictionary from note start tick to pitch point
//value is a tuple of (starttick, endtick, pitch points)
//Here starttick and endtick are project absolute tick, and pitch points are ms relative to the starttick
var pitchPointsPerNote = new Dictionary<int, Tuple<int,int,List<PitchPoint>>>();
var pitchPointsPerNote = new Dictionary<int, Tuple<int, int, List<PitchPoint>>>();
foreach (var phrase in phrases) {
var pitchStart = -phrase.leading;
//var ticks = Enumerable.Range(0, phrase.duration).Select(i => i * 5).ToArray();
Expand All @@ -508,27 +517,27 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do

//Reduce pitch point
var mustIncludeIndices = phrase.notes
.SelectMany(n => new[] {
n.position,
.SelectMany(n => new[] {
n.position,
n.duration>160 ? n.end-80 : n.position+n.duration/2 })
.Select(t=>(t-pitchStart)/pitchInterval)
.Select(t => (t - pitchStart) / pitchInterval)
.Prepend(0)
.Append(points.Count-1)
.Append(points.Count - 1)
.ToList();
//pairwise(mustIncludePointIndices)
points = mustIncludeIndices.Zip(mustIncludeIndices.Skip(1),
(a, b) => simplifyShape(points.GetRange(a,b-a),10))
.SelectMany(x=>x).Append(points[^1]).ToList();
points = mustIncludeIndices.Zip(mustIncludeIndices.Skip(1),
(a, b) => simplifyShape(points.GetRange(a, b - a), 10))
.SelectMany(x => x).Append(points[^1]).ToList();

//determine where to distribute pitch point
int idx = 0;
//note_boundary[i] is the index of the first pitch point after the end of note i
var note_boundaries = new int[phrase.notes.Length + 1];
note_boundaries[0] = 2;
foreach(int i in Enumerable.Range(0,phrase.notes.Length)) {
foreach (int i in Enumerable.Range(0, phrase.notes.Length)) {
var note = phrase.notes[i];
while(idx<points.Count
&& points[idx].X<note.end){
while (idx < points.Count
&& points[idx].X < note.end) {
idx++;
}
note_boundaries[i + 1] = idx;
Expand All @@ -537,32 +546,32 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
//otherwise, it is the index of the pitch point with minimal y-distance to the note
var adjusted_boundaries = new int[phrase.notes.Length + 1];
adjusted_boundaries[0] = 2;
foreach(int i in Enumerable.Range(0,phrase.notes.Length - 1)){
foreach (int i in Enumerable.Range(0, phrase.notes.Length - 1)) {
var note = phrase.notes[i];
var notePitch = note.tone*100;
var notePitch = note.tone * 100;
//var zero_point = points.FindIndex(note_boundaries[i], note_boundaries[i + 1] - note_boundaries[i], p => p.Y == 0);
var zero_point = Enumerable.Range(0,note_boundaries[i + 1] - note_boundaries[i])
.Select(j=>note_boundaries[i+1]-1-j)
.Where(j => (points[j].Y-notePitch) * (points[j-1].Y-notePitch) <= 0)
var zero_point = Enumerable.Range(0, note_boundaries[i + 1] - note_boundaries[i])
.Select(j => note_boundaries[i + 1] - 1 - j)
.Where(j => (points[j].Y - notePitch) * (points[j - 1].Y - notePitch) <= 0)
.DefaultIfEmpty(-1)
.First();
if(zero_point != -1){
if (zero_point != -1) {
adjusted_boundaries[i + 1] = zero_point + 1;
}else{
} else {
adjusted_boundaries[i + 1] = LastIndexOfMin(points, p => Math.Abs(p.Y - notePitch), note_boundaries[i], note_boundaries[i + 1]) + 2;
}
}
adjusted_boundaries[^1] = note_boundaries[^1];
//distribute pitch point to each note
foreach(int i in Enumerable.Range(0,phrase.notes.Length)) {
foreach (int i in Enumerable.Range(0, phrase.notes.Length)) {
var note = phrase.notes[i];
var pitch = points.GetRange(adjusted_boundaries[i]-2,adjusted_boundaries[i + 1]-(adjusted_boundaries[i]-2))
var pitch = points.GetRange(adjusted_boundaries[i] - 2, adjusted_boundaries[i + 1] - (adjusted_boundaries[i] - 2))
.Select(p => new PitchPoint(
(float)timeAxis.MsBetweenTickPos(note.position + part.position, p.X + part.position),
(float)(p.Y - note.tone * 100) / 10,
p.shape))
.ToList();
pitchPointsPerNote[note.position + phrase.position - part.position]
pitchPointsPerNote[note.position + phrase.position - part.position]
= Tuple.Create(
points[adjusted_boundaries[i] - 2].X + phrase.position,
points[adjusted_boundaries[i + 1] - 1].X + phrase.position,
Expand All @@ -571,35 +580,35 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
}
docManager.StartUndoGroup(true);
//Apply pitch points to notes
foreach(var note in notes) {
foreach (var note in notes) {
if (pitchPointsPerNote.TryGetValue(note.position, out var tickRangeAndPitch)) {
var pitch = tickRangeAndPitch.Item3;
docManager.ExecuteCmd(new ResetPitchPointsCommand(part, note));
int index = 0;
foreach(var point in pitch) {
foreach (var point in pitch) {
docManager.ExecuteCmd(new AddPitchPointCommand(part, note, point, index));
index++;
}
docManager.ExecuteCmd(new DeletePitchPointCommand(part, note, index));
docManager.ExecuteCmd(new DeletePitchPointCommand(part, note, index));
var lastPitch = note.pitch.data[^1];
docManager.ExecuteCmd(new MovePitchPointCommand(part, lastPitch ,0, -lastPitch.Y));
var lastPitch = note.pitch.data[^1];
docManager.ExecuteCmd(new MovePitchPointCommand(part, lastPitch, 0, -lastPitch.Y));

}
}
//Erase PITD curve that has been converted to pitch points
foreach(var note in notes) {
foreach (var note in notes) {
if (pitchPointsPerNote.TryGetValue(note.position, out var tickRangeAndPitch)) {
var start = tickRangeAndPitch.Item1 - part.position;
var end = tickRangeAndPitch.Item2 - part.position;
docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD,
start, 0,
docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD,
start, 0,
start, 0));
docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD,
end, 0,
docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD,
end, 0,
end, 0));
docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD,
start, 0,
docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD,
start, 0,
end, 0));
}
}
Expand All @@ -610,7 +619,7 @@ public void Run(UProject project, UVoicePart part, List<UNote> selectedNotes, Do
}
}
//Clear MOD+ expressions for selected notes
foreach(var phoneme in part.phonemes) {
foreach (var phoneme in part.phonemes) {
if (phoneme.Parent != null && notes.Contains(phoneme.Parent)) {
docManager.ExecuteCmd(new SetPhonemeExpressionCommand(DocManager.Inst.Project, project.tracks[part.trackNo], part, phoneme, "mod+", null));
}
Expand Down
Loading