Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
17 changes: 14 additions & 3 deletions OpenUtau.Core/DiffSinger/DiffSingerPitch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
DiffSingerSpeakerEmbedManager speakerEmbedManager;
const string PEXP = DiffSingerUtils.PEXP;

public float FrameMs => frameMs;

public DsPitch(string rootPath)
{
this.rootPath = rootPath;
Expand Down Expand Up @@ -107,7 +109,7 @@
return token;
}

public RenderPitchResult Process(RenderPhrase phrase){
public RenderPitchResult Process(RenderPhrase phrase, HashSet<int> retakeNoteIndexes = null, float[] existingPitch = null){

Check warning on line 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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.

Check warning on line 112 in OpenUtau.Core/DiffSinger/DiffSingerPitch.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.
Comment thread
KakaruHayate marked this conversation as resolved.
Outdated
var startMs = phrase.phones[0].positionMs - DiffSingerUtils.GetHeadMs(frameMs);
int headFrames = DiffSingerUtils.headFrames;
int tailFrames = DiffSingerUtils.tailFrames;
Expand Down Expand Up @@ -251,6 +253,13 @@
.ToList();
var pitch = Enumerable.Repeat(60f, totalFrames).ToArray();
var retake = Enumerable.Repeat(true, totalFrames).ToArray();
if (retakeNoteIndexes != null && existingPitch != null) {
retake = DiffSingerRetake.BuildRetakeFrameMask(
note_dur, phrase.notes.Length, retakeNoteIndexes, totalFrames);
for (int i = 0; i < totalFrames && i < existingPitch.Length; i++) {
pitch[i] = existingPitch[i];
}
}
Comment thread
KakaruHayate marked this conversation as resolved.
var pitchInputs = new List<NamedOnnxValue>();
pitchInputs.Add(NamedOnnxValue.CreateFromTensor("encoder_out", encoder_out));
pitchInputs.Add(NamedOnnxValue.CreateFromTensor("note_midi",
Expand Down Expand Up @@ -322,14 +331,16 @@
.Select(i=>(float)phrase.timeAxis.MsPosToTickPos(startMs + i*frameMs) - phrase.position)
.Append((float)phrase.duration + 1)
.ToArray(),
tones = pitch_out.Append(pitch_out[^1]).ToArray()
tones = pitch_out.Append(pitch_out[^1]).ToArray(),
retakeMask = retakeNoteIndexes != null ? retake.Append(retake[^1]).ToArray() : null,
};
}else{
return new RenderPitchResult{
ticks = Enumerable.Range(0,totalFrames)
.Select(i=>(float)phrase.timeAxis.MsPosToTickPos(startMs + i*frameMs) - phrase.position)
.ToArray(),
tones = pitch_out
tones = pitch_out,
retakeMask = retakeNoteIndexes != null ? retake : null,
};
}
}
Expand Down
32 changes: 32 additions & 0 deletions OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,38 @@ public RenderPitchResult LoadRenderedPitch(RenderPhrase phrase) {
}
}

public RenderPitchResult LoadRenderedPitch(RenderPhrase phrase, HashSet<int> selectedNotePositions) {
if (!Preferences.Default.DiffSingerLocalRetaking) {
return LoadRenderedPitch(phrase);
}
DiffSingerSinger singer = (DiffSingerSinger) phrase.singer;
if (!singer.HasPitchPredictor) {
throw new Exception("This singer has no pitch predictor.");
}
var pitchPredictor = singer.getPitchPredictor()!;
var noteRelativePositions = new int[phrase.notes.Length];
for (int i = 0; i < phrase.notes.Length; i++) {
noteRelativePositions[i] = phrase.notes[i].position;
}
var retakeNoteIndexes = DiffSingerRetake.MapSelectedPositionsToNoteIndexes(
phrase.position, noteRelativePositions, selectedNotePositions);
if (retakeNoteIndexes.Count == 0 || retakeNoteIndexes.Count == phrase.notes.Length) {
lock (pitchPredictor) {
return pitchPredictor.Process(phrase);
}
}
var frameMs = pitchPredictor.FrameMs;
int headFrames = DiffSingerUtils.headFrames;
int tailFrames = DiffSingerUtils.tailFrames;
var ph_dur = DiffSingerUtils.PaddedPhoneDurations(phrase, frameMs, headFrames, tailFrames);
int totalFrames = ph_dur.Sum();
var existingPitch = DiffSingerUtils.SampleCurve(phrase, phrase.pitches, 0, frameMs, totalFrames, headFrames, tailFrames,
x => x * 0.01).Select(f => (float)f).ToArray();
lock (pitchPredictor) {
return pitchPredictor.Process(phrase, retakeNoteIndexes, existingPitch);
}
}

public List<RenderRealCurveResult> LoadRenderedRealCurves(RenderPhrase phrase) {
if (!Preferences.Default.DiffSingerTensorCache) {
throw new Exception("Please enable DiffSinger tensor cache and re-render the phrase to display correct base curves.");
Expand Down
57 changes: 57 additions & 0 deletions OpenUtau.Core/DiffSinger/DiffSingerRetake.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Collections.Generic;

namespace OpenUtau.Core.DiffSinger {
public static class DiffSingerRetake {
public static HashSet<int> MapSelectedPositionsToNoteIndexes(
int phrasePosition,
IReadOnlyList<int> noteRelativePositions,
IReadOnlyCollection<int> selectedAbsolutePositions) {
var result = new HashSet<int>();
if (selectedAbsolutePositions == null || selectedAbsolutePositions.Count == 0) {
return result;
}
Comment thread
KakaruHayate marked this conversation as resolved.
var lookup = selectedAbsolutePositions as ISet<int> ?? new HashSet<int>(selectedAbsolutePositions);
for (int i = 0; i < noteRelativePositions.Count; i++) {
if (lookup.Contains(phrasePosition + noteRelativePositions[i])) {
result.Add(i);
}
}
return result;
}

public static bool[] BuildRetakeFrameMask(
IReadOnlyList<int> paddedNoteDurations,
int realNoteCount,
IReadOnlyCollection<int> retakeNoteIndexes,
int totalFrames) {
var mask = new bool[totalFrames];
if (retakeNoteIndexes == null || retakeNoteIndexes.Count == 0 || paddedNoteDurations.Count == 0) {
return mask;
}
Comment thread
KakaruHayate marked this conversation as resolved.
var lookup = retakeNoteIndexes as ISet<int> ?? new HashSet<int>(retakeNoteIndexes);
int padded = paddedNoteDurations.Count;
int frameOffset = 0;
for (int noteIdx = 0; noteIdx < padded; noteIdx++) {
int realIdx;
if (noteIdx == 0) {
realIdx = 0;
} else if (noteIdx == padded - 1) {
realIdx = realNoteCount - 1;
} else {
realIdx = noteIdx - 1;
}
bool shouldRetake = lookup.Contains(realIdx);
int dur = paddedNoteDurations[noteIdx];
for (int f = 0; f < dur; f++) {
int fi = frameOffset + f;
if (fi < totalFrames) {
mask[fi] = shouldRetake;
}
}
frameOffset += dur;
}
return mask;
}
}
}

5 changes: 4 additions & 1 deletion OpenUtau.Core/Editing/NoteBatchEdits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ public void RunAsync(
var commands = new List<SetCurveCommand>();
for (int ph_i = phrases.Count() - 1; ph_i >= 0; ph_i--) {
var phrase = phrases[ph_i];
var result = renderer.LoadRenderedPitch(phrase);
var result = renderer.LoadRenderedPitch(phrase, positions);
if (result == null) {
continue;
}
Expand All @@ -502,6 +502,9 @@ public void RunAsync(
if (result.tones[i] < 0) {
continue;
}
if (result.retakeMask != null && !result.retakeMask[i]) {
continue;
}
Comment thread
KakaruHayate marked this conversation as resolved.
Outdated
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
Expand Down
6 changes: 6 additions & 0 deletions OpenUtau.Core/Render/IRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public class RenderPitchResult {
/// Semitone values in MIDI scale.
/// </summary>
public float[] tones;

/// <summary>
/// Per-frame mask indicating retaken frames. Null means full retake.
/// </summary>
public bool[] retakeMask;
Comment thread
KakaruHayate marked this conversation as resolved.
Outdated
}

public class RenderRealCurveResult {
Expand Down Expand Up @@ -70,6 +75,7 @@ public interface IRenderer {
RenderResult Layout(RenderPhrase phrase);
Task<RenderResult> Render(RenderPhrase phrase, Progress progress, int trackNo, CancellationTokenSource cancellation, bool isPreRender = false);
RenderPitchResult LoadRenderedPitch(RenderPhrase phrase);
RenderPitchResult LoadRenderedPitch(RenderPhrase phrase, HashSet<int> selectedNotePositions) { return LoadRenderedPitch(phrase); }
List<RenderRealCurveResult> LoadRenderedRealCurves(RenderPhrase phrase) { return new List<RenderRealCurveResult>(0);}
UExpressionDescriptor[] GetSuggestedExpressions(USinger singer, URenderSettings renderSettings);
}
Expand Down
1 change: 1 addition & 0 deletions OpenUtau.Core/Util/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public class SerializablePreferences {
public int DiffSingerStepsPitch = 10;
public bool DiffSingerTensorCache = true;
public bool DiffSingerLangCodeHide = false;
public bool DiffSingerLocalRetaking = false;
public bool SkipRenderingMutedTracks = false;
public string Language = string.Empty;
public string? SortingOrder = null;
Expand Down
136 changes: 136 additions & 0 deletions OpenUtau.Test/Core/DiffSinger/DiffSingerRetakeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System.Collections.Generic;
using System.Linq;
using OpenUtau.Core.DiffSinger;
using Xunit;

namespace OpenUtau.Core {
public class DiffSingerRetakeTest {
Comment thread
KakaruHayate marked this conversation as resolved.
Outdated
[Fact]
public void MapSelectedPositionsToNoteIndexes_PicksMatchingNotes() {
var noteRel = new[] { 0, 480, 960, 1440 };
var selected = new HashSet<int> { 100 + 480, 100 + 1440 };

var result = DiffSingerRetake.MapSelectedPositionsToNoteIndexes(100, noteRel, selected);

Assert.Equal(new HashSet<int> { 1, 3 }, result);
}

[Fact]
public void MapSelectedPositionsToNoteIndexes_ReturnsEmptyWhenNoneSelected() {
var noteRel = new[] { 0, 480 };
var result = DiffSingerRetake.MapSelectedPositionsToNoteIndexes(0, noteRel, new HashSet<int>());
Assert.Empty(result);
}

[Fact]
public void MapSelectedPositionsToNoteIndexes_HandlesNullSelected() {
var noteRel = new[] { 0, 480 };
var result = DiffSingerRetake.MapSelectedPositionsToNoteIndexes(0, noteRel, null);
Assert.Empty(result);
}

[Fact]
public void BuildRetakeFrameMask_AllSelected_AllTrue() {
var paddedDurations = new[] { 2, 5, 5, 2 };
var totalFrames = paddedDurations.Sum();
var indexes = new HashSet<int> { 0, 1 };

var mask = DiffSingerRetake.BuildRetakeFrameMask(paddedDurations, 2, indexes, totalFrames);

Assert.Equal(totalFrames, mask.Length);
Assert.All(mask, b => Assert.True(b));
}

[Fact]
public void BuildRetakeFrameMask_NoneSelected_AllFalse() {
var paddedDurations = new[] { 2, 5, 5, 2 };
var totalFrames = paddedDurations.Sum();
var indexes = new HashSet<int>();

var mask = DiffSingerRetake.BuildRetakeFrameMask(paddedDurations, 2, indexes, totalFrames);

Assert.Equal(totalFrames, mask.Length);
Assert.All(mask, b => Assert.False(b));
}

[Fact]
public void BuildRetakeFrameMask_PartialSelected_RespectsHeadTailPaddingShift() {
// 3 real notes, padded with head + tail → 5 padded "note durations".
// Mapping: padded[0] → real 0 (head), padded[1] → real 0, padded[2] → real 1,
// padded[3] → real 2, padded[4] → real 2 (tail).
var paddedDurations = new[] { 2, 3, 4, 3, 2 }; // 14 frames total
int totalFrames = paddedDurations.Sum();
var indexes = new HashSet<int> { 1 }; // retake only middle real note

var mask = DiffSingerRetake.BuildRetakeFrameMask(paddedDurations, 3, indexes, totalFrames);

// padded[0] (frames 0-1, head→real 0) → false
Assert.False(mask[0]);
Assert.False(mask[1]);
// padded[1] (frames 2-4, real 0) → false
Assert.False(mask[2]);
Assert.False(mask[4]);
// padded[2] (frames 5-8, real 1) → true
Assert.True(mask[5]);
Assert.True(mask[8]);
// padded[3] (frames 9-11, real 2) → false
Assert.False(mask[9]);
Assert.False(mask[11]);
// padded[4] (frames 12-13, tail→real 2) → false
Assert.False(mask[12]);
Assert.False(mask[13]);
}

[Fact]
public void BuildRetakeFrameMask_FirstRealNoteSelected_HeadPadIncluded() {
// Selecting real note 0 should mark both head (padded[0]) and padded[1] frames.
var paddedDurations = new[] { 2, 3, 4, 3, 2 };
int totalFrames = paddedDurations.Sum();
var indexes = new HashSet<int> { 0 };

var mask = DiffSingerRetake.BuildRetakeFrameMask(paddedDurations, 3, indexes, totalFrames);

Assert.True(mask[0]);
Assert.True(mask[1]); // head padded → real 0
Assert.True(mask[2]);
Assert.True(mask[4]); // padded[1] → real 0
Assert.False(mask[5]); // padded[2] → real 1, not selected
}

[Fact]
public void BuildRetakeFrameMask_LastRealNoteSelected_TailPadIncluded() {
var paddedDurations = new[] { 2, 3, 4, 3, 2 };
int totalFrames = paddedDurations.Sum();
var indexes = new HashSet<int> { 2 }; // last real note

var mask = DiffSingerRetake.BuildRetakeFrameMask(paddedDurations, 3, indexes, totalFrames);

Assert.False(mask[8]);
Assert.True(mask[9]); // padded[3] → real 2
Assert.True(mask[11]);
Assert.True(mask[12]); // padded[4] tail → real 2
Assert.True(mask[13]);
}

[Fact]
public void BuildRetakeFrameMask_ClampsFramesPastTotal() {
// paddedDurations sum to 10 but totalFrames is 8 (simulating FitDurationSum trim).
var paddedDurations = new[] { 2, 4, 4 };
var indexes = new HashSet<int> { 0 };

var mask = DiffSingerRetake.BuildRetakeFrameMask(paddedDurations, 1, indexes, 8);

Assert.Equal(8, mask.Length);
// Should not throw; frames past totalFrames silently dropped.
Assert.True(mask[0]);
Assert.True(mask[5]);
}

[Fact]
public void BuildRetakeFrameMask_EmptyDurations_ReturnsAllFalse() {
var mask = DiffSingerRetake.BuildRetakeFrameMask(new int[0], 0, new HashSet<int> { 0 }, 4);
Assert.Equal(4, mask.Length);
Assert.All(mask, b => Assert.False(b));
}
}
}
1 change: 1 addition & 0 deletions OpenUtau/Strings/Strings.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ Warning: this option removes custom presets.</system:String>
<system:String x:Key="prefs.rendering.diffsingersteps">DiffSinger Render Steps for Acoustic</system:String>
<system:String x:Key="prefs.rendering.diffsingerstepspitch">DiffSinger Render Steps for Pitch</system:String>
<system:String x:Key="prefs.rendering.diffsingerstepsvariance">DiffSinger Render Steps for Variance</system:String>
<system:String x:Key="prefs.rendering.diffsingerpitchlocalretaking">DiffSinger Pitch Local Retaking</system:String>
<system:String x:Key="prefs.rendering.onnxgpu">GPU</system:String>
<system:String x:Key="prefs.rendering.onnxrunner">Machine Learning Runner</system:String>
<system:String x:Key="prefs.rendering.phasecomp">Phase Compensation</system:String>
Expand Down
1 change: 1 addition & 0 deletions OpenUtau/Strings/Strings.zh-CN.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ Syntax: prefix,suffix</system:String>-->
<!--<system:String x:Key="prefs.rendering.diffsingersteps">DiffSinger Render Steps for Acoustic</system:String>-->
<!--<system:String x:Key="prefs.rendering.diffsingerstepspitch">DiffSinger Render Steps for Pitch</system:String>-->
<!--<system:String x:Key="prefs.rendering.diffsingerstepsvariance">DiffSinger Render Steps for Variance</system:String>-->
<system:String x:Key="prefs.rendering.diffsingerpitchlocalretaking">DiffSinger 音高局部重录</system:String>
<!--<system:String x:Key="prefs.rendering.onnxgpu">GPU</system:String>-->
<system:String x:Key="prefs.rendering.onnxrunner">机器学习运行器</system:String>
<system:String x:Key="prefs.rendering.phasecomp">相位修正</system:String>
Expand Down
7 changes: 7 additions & 0 deletions OpenUtau/ViewModels/PreferencesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public int SafeMaxThreadCount {
[Reactive] public double DiffSingerDepth { get; set; }
[Reactive] public bool DiffSingerTensorCache { get; set; }
[Reactive] public bool DiffSingerLangCodeHide { get; set; }
[Reactive] public bool DiffSingerLocalRetaking { get; set; }

// Advanced
[Reactive] public bool RememberMid { get; set; }
Expand Down Expand Up @@ -175,6 +176,7 @@ public PreferencesViewModel() {
DiffSingerStepsPitch = Preferences.Default.DiffSingerStepsPitch;
DiffSingerTensorCache = Preferences.Default.DiffSingerTensorCache;
DiffSingerLangCodeHide = Preferences.Default.DiffSingerLangCodeHide;
DiffSingerLocalRetaking = Preferences.Default.DiffSingerLocalRetaking;
SkipRenderingMutedTracks = Preferences.Default.SkipRenderingMutedTracks;
ThemeName = Preferences.Default.ThemeName;
PenPlusDefault = Preferences.Default.PenPlusDefault;
Expand Down Expand Up @@ -398,6 +400,11 @@ public PreferencesViewModel() {
Preferences.Default.DiffSingerLangCodeHide = useCache;
Preferences.Save();
});
this.WhenAnyValue(vm => vm.DiffSingerLocalRetaking)
.Subscribe(value => {
Preferences.Default.DiffSingerLocalRetaking = value;
Preferences.Save();
});
this.WhenAnyValue(vm => vm.SkipRenderingMutedTracks)
.Subscribe(skipRenderingMutedTracks => {
Preferences.Default.SkipRenderingMutedTracks = skipRenderingMutedTracks;
Expand Down
4 changes: 4 additions & 0 deletions OpenUtau/Views/PreferencesDialog.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@
<TextBlock Text="{DynamicResource prefs.appearance.diffsingerlangcodehide}" HorizontalAlignment="Left" />
<ToggleSwitch IsChecked="{Binding DiffSingerLangCodeHide}" />
</Grid>
<Grid Margin="0,5,0,0">
<TextBlock Text="{DynamicResource prefs.rendering.diffsingerpitchlocalretaking}" HorizontalAlignment="Left" />
<ToggleSwitch IsChecked="{Binding DiffSingerLocalRetaking}" />
</Grid>
</StackPanel>

<!-- Advanced -->
Expand Down
Loading