Skip to content
Open
Show file tree
Hide file tree
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
9 changes: 8 additions & 1 deletion OpenUtau.Core/Commands/Notifications.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,14 @@
}

public class PreRenderNotification : UNotification {
public override string ToString() => $"Pre-render notification.";
public readonly int focusTick;

public PreRenderNotification(UPart part = null, int focusTick = -1) {

Check warning on line 228 in OpenUtau.Core/Commands/Notifications.cs

View workflow job for this annotation

GitHub Actions / pr-test (macos-latest, osx-arm64)

Cannot convert null literal to non-nullable reference type.

Check warning on line 228 in OpenUtau.Core/Commands/Notifications.cs

View workflow job for this annotation

GitHub Actions / pr-test (ubuntu-latest, linux-x64)

Cannot convert null literal to non-nullable reference type.

Check warning on line 228 in OpenUtau.Core/Commands/Notifications.cs

View workflow job for this annotation

GitHub Actions / pr-test (windows-latest, win-x64)

Cannot convert null literal to non-nullable reference type.

Check warning on line 228 in OpenUtau.Core/Commands/Notifications.cs

View workflow job for this annotation

GitHub Actions / pr-test (macos-15-intel, osx-x64)

Cannot convert null literal to non-nullable reference type.
this.part = part;
this.focusTick = focusTick;
}

public override string ToString() => "Pre-render notification.";
}

public class PartRenderedNotification : UNotification {
Expand Down
24 changes: 23 additions & 1 deletion OpenUtau.Core/PlaybackManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ private PlaybackManager() {
double startMs;
public int StartTick => DocManager.Inst.Project.timeAxis.MsPosToTickPos(startMs);
CancellationTokenSource renderCancellation;
UVoicePart preRenderFocusPart;
int preRenderFocusTick = -1;

public Audio.IAudioOutput AudioOutput { get; set; } = new Audio.DummyAudioOutput();
public bool OutputActive => AudioOutput.PlaybackState == PlaybackState.Playing;
Expand Down Expand Up @@ -375,7 +377,10 @@ private void CheckFileWritable(string filePath) {

void SchedulePreRender() {
Log.Information("SchedulePreRender");
var engine = new RenderEngine(DocManager.Inst.Project);
var engine = new RenderEngine(
DocManager.Inst.Project,
focusPart: preRenderFocusPart,
focusTick: preRenderFocusTick);
engine.PreRenderProject(ref renderCancellation);
}

Expand All @@ -400,7 +405,24 @@ public void OnNext(UCommand cmd, bool isUndo) {
} else if (cmd is LoadProjectNotification) {
StopPlayback();
renderCancellation?.Cancel();
preRenderFocusPart = null;
preRenderFocusTick = -1;
DocManager.Inst.ExecuteCmd(new SetPlayPosTickNotification(0));
} else if (cmd is LoadPartNotification loadPart) {
preRenderFocusPart = loadPart.part as UVoicePart;
preRenderFocusTick = loadPart.tick;
} else if (cmd is FocusNoteNotification focusNote) {
preRenderFocusPart = focusNote.part as UVoicePart;
preRenderFocusTick = focusNote.part?.position + focusNote.note.position ?? preRenderFocusTick;
} else if (cmd is SetPlayPosTickNotification setPlayPosTick) {
preRenderFocusTick = setPlayPosTick.playPosTick;
} else if (cmd is PreRenderNotification preRender) {
if (preRender.part is UVoicePart voicePart) {
preRenderFocusPart = voicePart;
}
if (preRender.focusTick >= 0) {
preRenderFocusTick = preRender.focusTick;
}
}
if (cmd is PreRenderNotification || cmd is LoadProjectNotification) {
if (Util.Preferences.Default.PreRender) {
Expand Down
3 changes: 3 additions & 0 deletions OpenUtau.Core/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("OpenUtau.Test")]
79 changes: 68 additions & 11 deletions OpenUtau.Core/Render/RenderEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,22 @@ class RenderEngine {
readonly int startTick;
readonly int endTick;
readonly int trackNo;
readonly UVoicePart focusPart;
readonly int focusTick;

public RenderEngine(UProject project, int startTick = 0, int endTick = -1, int trackNo = -1) {
public RenderEngine(
UProject project,
int startTick = 0,
int endTick = -1,
int trackNo = -1,
UVoicePart focusPart = null,
int focusTick = -1) {
this.project = project;
this.startTick = startTick;
this.endTick = endTick;
this.trackNo = trackNo;
this.focusPart = focusPart;
this.focusTick = focusTick;
}

// for playback or export
Expand Down Expand Up @@ -220,21 +230,24 @@ private void RenderRequests(
}
var tuples = requests
.SelectMany(req => req.phrases
.Zip(req.sources, (phrase, source) => Tuple.Create(phrase, source, req)))
.Zip(req.sources, (phrase, source) => (phrase, source, request: req)))
.ToArray();
if (tuples.Length == 0) {
return;
}
if (playing) {
var orderedTuples = tuples
.Where(tuple => tuple.Item1.end > startTick)
.OrderBy(tuple => tuple.Item1.end)
.Concat(tuples.Where(tuple => tuple.Item1.end <= startTick))
.ToArray();
tuples = orderedTuples;
tuples = OrderForPlayback(tuples);
} else if (focusPart != null || focusTick >= 0) {
tuples = OrderForPreRender(tuples);
}
var progress = new Progress(tuples.Sum(t => t.Item1.phones.Length));
foreach (var tuple in tuples) {
var phrase = tuple.Item1;
var source = tuple.Item2;
var request = tuple.Item3;
if (cancellation.IsCancellationRequested) {
break;
}
var phrase = tuple.phrase;
var source = tuple.source;
var request = tuple.request;
var task = phrase.renderer.Render(phrase, progress, request.trackNo, cancellation, true);
task.Wait();
if (cancellation.IsCancellationRequested) {
Expand All @@ -249,6 +262,50 @@ private void RenderRequests(
progress.Clear();
}

private (RenderPhrase phrase, WaveSource source, RenderPartRequest request)[] OrderForPlayback(
(RenderPhrase phrase, WaveSource source, RenderPartRequest request)[] tuples) {
double playbackStartMs = project.timeAxis.TickPosToMsPos(startTick);
return tuples
.Select((tuple, index) => (tuple, index))
.OrderBy(item => RenderPriority.PlaybackBucket(
item.tuple.source.offsetMs, item.tuple.source.EndMs, playbackStartMs))
.ThenBy(item => RenderPriority.PlaybackDistance(
item.tuple.source.offsetMs, item.tuple.source.EndMs, playbackStartMs))
.ThenBy(item => item.index)
.Select(item => item.tuple)
.ToArray();
}

private (RenderPhrase phrase, WaveSource source, RenderPartRequest request)[] OrderForPreRender(
(RenderPhrase phrase, WaveSource source, RenderPartRequest request)[] tuples) {
return tuples
.Select((tuple, index) => (tuple, index))
.OrderBy(item => PreRenderAttentionBucket(item.tuple))
.ThenBy(item => PreRenderAttentionDistance(item.tuple.phrase))
.ThenBy(item => item.index)
.Select(item => item.tuple)
.ToArray();
}

private int PreRenderAttentionBucket(
(RenderPhrase phrase, WaveSource source, RenderPartRequest request) tuple) {
bool isPriorityPart = focusPart != null && ReferenceEquals(tuple.request.part, focusPart);
bool overlapsPriority = focusTick >= 0 &&
tuple.phrase.position <= focusTick &&
tuple.phrase.end > focusTick;
bool isAfterPriorityStart = focusTick < 0 || tuple.phrase.end > focusTick;
return RenderPriority.PreRenderBucket(
isPriorityPart,
overlapsPriority,
isAfterPriorityStart);
}

private int PreRenderAttentionDistance(RenderPhrase phrase) {
return focusTick >= 0
? RenderPriority.PreRenderDistance(phrase.position, phrase.end, focusTick)
: 0;
}

public static void ReleaseSourceTemp() {
VoicebankFiles.Inst.ReleaseSourceTemp();
}
Expand Down
48 changes: 48 additions & 0 deletions OpenUtau.Core/Render/RenderPriority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;

namespace OpenUtau.Core.Render {
internal static class RenderPriority {
internal static int PlaybackBucket(double sourceStartMs, double sourceEndMs, double playbackStartMs) {
if (sourceStartMs <= playbackStartMs && sourceEndMs > playbackStartMs) {
return 0;
}
return sourceStartMs >= playbackStartMs ? 1 : 2;
}

internal static double PlaybackDistance(double sourceStartMs, double sourceEndMs, double playbackStartMs) {
if (sourceStartMs <= playbackStartMs && sourceEndMs > playbackStartMs) {
return Math.Max(0, playbackStartMs - sourceStartMs);
}
if (sourceStartMs >= playbackStartMs) {
return sourceStartMs - playbackStartMs;
}
return playbackStartMs - sourceEndMs;
}

internal static int PreRenderBucket(
bool isPriorityPart,
bool overlapsPriority,
bool isAfterPriorityStart) {
if (isPriorityPart && overlapsPriority) {
return 0;
}
if (isPriorityPart) {
return 1;
}
if (isAfterPriorityStart) {
return 2;
}
return 3;
}

internal static int PreRenderDistance(int phraseStartTick, int phraseEndTick, int priorityStartTick) {
if (phraseStartTick <= priorityStartTick && phraseEndTick > priorityStartTick) {
return 0;
}
if (phraseStartTick >= priorityStartTick) {
return phraseStartTick - priorityStartTick;
}
return priorityStartTick - phraseEndTick;
}
}
}
38 changes: 38 additions & 0 deletions OpenUtau.Test/Core/Render/RenderPriorityTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Xunit;

namespace OpenUtau.Core.Render {
public class RenderPriorityTest {
[Fact]
public void PlaybackBucket_PrioritizesCurrentThenFutureThenPast() {
Assert.Equal(0, RenderPriority.PlaybackBucket(100, 200, 150));
Assert.Equal(1, RenderPriority.PlaybackBucket(200, 300, 150));
Assert.Equal(2, RenderPriority.PlaybackBucket(0, 100, 150));
}

[Fact]
public void PlaybackDistance_PrioritizesEarlierOffsetInCurrentBucket() {
Assert.Equal(50, RenderPriority.PlaybackDistance(100, 200, 150));
Assert.Equal(50, RenderPriority.PlaybackDistance(200, 300, 150));
Assert.Equal(50, RenderPriority.PlaybackDistance(0, 100, 150));
}

[Fact]
public void PreRenderBucket_PrioritizesFocusedPartAtAttentionTick() {
Assert.Equal(0, RenderPriority.PreRenderBucket(
isPriorityPart: true, overlapsPriority: true, isAfterPriorityStart: true));
Assert.Equal(1, RenderPriority.PreRenderBucket(
isPriorityPart: true, overlapsPriority: false, isAfterPriorityStart: true));
Assert.Equal(2, RenderPriority.PreRenderBucket(
isPriorityPart: false, overlapsPriority: false, isAfterPriorityStart: true));
Assert.Equal(3, RenderPriority.PreRenderBucket(
isPriorityPart: false, overlapsPriority: false, isAfterPriorityStart: false));
}

[Fact]
public void PreRenderDistance_PrioritizesPhraseContainingAttentionTick() {
Assert.Equal(0, RenderPriority.PreRenderDistance(100, 200, 150));
Assert.Equal(50, RenderPriority.PreRenderDistance(200, 300, 150));
Assert.Equal(50, RenderPriority.PreRenderDistance(0, 100, 150));
}
}
}
Loading