Skip to content

Commit dfa18bb

Browse files
Merge branch 'stakira:master' into master
2 parents 4fb1528 + ec23a29 commit dfa18bb

67 files changed

Lines changed: 1641 additions & 884 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
on: workflow_dispatch
2+
3+
jobs:
4+
pr-test:
5+
runs-on: ${{ matrix.os.runs-on }}
6+
7+
strategy:
8+
matrix:
9+
os:
10+
- runs-on: windows-latest
11+
arch: win-x64
12+
rid: win-x64
13+
- runs-on: ubuntu-latest
14+
arch: linux-x64
15+
rid: linux-x64
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-dotnet@v4
20+
with:
21+
dotnet-version: "6.0.x"
22+
- name: Restore
23+
run: dotnet restore OpenUtau -r ${{ matrix.os.rid }}
24+
- name: test
25+
run: dotnet test OpenUtau.Test
26+
27+
- name: Download DirectML.dll
28+
shell: powershell
29+
run: Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/Microsoft.AI.DirectML/1.12.0" -OutFile "Microsoft.AI.DirectML.nupkg"
30+
if: matrix.os.arch == 'win-x64'
31+
- name: Extract DirectML.dll
32+
shell: cmd
33+
run: |
34+
mkdir Microsoft.AI.DirectML
35+
tar -xf Microsoft.AI.DirectML.nupkg -C Microsoft.AI.DirectML
36+
if: matrix.os.arch == 'win-x64'
37+
38+
- name: Build non-mac
39+
run: dotnet publish OpenUtau -c Release -r ${{ matrix.os.rid }} --self-contained true -o bin/${{ matrix.os.arch }}/
40+
if: matrix.os.arch != 'osx-x64'
41+
- name: upload non-mac build
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: OpenUtau-${{ matrix.os.arch }}
45+
path: bin/${{ matrix.os.arch }}
46+
if: matrix.os.arch != 'osx-x64'
47+
48+
- name: Setup Node.js
49+
uses: actions/setup-node@v4
50+
if: matrix.os.arch == 'osx-x64'
51+
- name: Build mac
52+
run: |
53+
dotnet msbuild OpenUtau -t:BundleApp -p:Configuration=Release -p:RuntimeIdentifier=${{ matrix.os.rid }} -p:UseAppHost=true -p:OutputPath=../bin/${{ matrix.os.arch }}/
54+
cp OpenUtau/Assets/OpenUtau.icns bin/${{ matrix.os.arch }}/publish/OpenUtau.app/Contents/Resources/
55+
npm install -g create-dmg
56+
create-dmg bin/osx-x64/publish/OpenUtau.app
57+
mv *.dmg OpenUtau-osx-x64.dmg
58+
codesign -fvs - OpenUtau-osx-x64.dmg
59+
if: matrix.os.arch == 'osx-x64'
60+
- name: Upload mac build
61+
uses: actions/upload-artifact@v4
62+
with:
63+
name: OpenUtau-${{ matrix.os.arch }}
64+
path: OpenUtau-osx-x64.dmg
65+
if: matrix.os.arch == 'osx-x64'

OpenUtau.Core/Api/G2pDictionary.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public Builder AddSymbol(string symbol, string type) {
7777
phonemeSymbols[symbol] = type == "vowel";
7878
if(type == "semivowel" || type == "liquid") {
7979
glideSymbols.Add(symbol);
80+
} else {
81+
glideSymbols.Remove(symbol);
8082
}
8183
return this;
8284
}
@@ -88,6 +90,8 @@ public Builder AddSymbol(string symbol, bool isVowel, bool isGlide) {
8890
phonemeSymbols[symbol] = isVowel;
8991
if (isGlide && !isVowel) {
9092
glideSymbols.Add(symbol);
93+
} else {
94+
glideSymbols.Remove(symbol);
9195
}
9296
return this;
9397
}

OpenUtau.Core/Classic/ClassicRenderer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public Task<RenderResult> RenderInternal(RenderPhrase phrase, Progress progress,
7272
item.resampler.DoResamplerReturnsFile(item, Log.Logger);
7373
}
7474
if (!File.Exists(item.outputFile)) {
75-
throw new InvalidDataException($"{item.resampler} failed to resample \"{item.phone.phoneme}\"");
75+
DocManager.Inst.Project.timeAxis.TickPosToBarBeat(item.phrase.position + item.phone.position, out int bar, out int beat, out int tick);
76+
throw new InvalidDataException($"{item.resampler} failed to resample \"{item.phone.phoneme}\" at {bar}:{beat}.{string.Format("{0:000}", tick)}");
7677
}
7778
if (!(item.resampler is WorldlineResampler)) {
7879
VoicebankFiles.Inst.CopyBackMetaFiles(item.inputFile, item.inputTemp);

OpenUtau.Core/Classic/ExeWavtool.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ class ExeWavtool : IWavtool {
1616
readonly StringBuilder sb = new StringBuilder();
1717
readonly string filePath;
1818
readonly string name;
19+
private Encoding osEncoding;
1920

2021
public ExeWavtool(string filePath, string basePath) {
2122
this.filePath = filePath;
2223
name = Path.GetRelativePath(basePath, filePath);
24+
osEncoding = OS.IsWindows() ? Encoding.GetEncoding(0) : Encoding.UTF8;
2325
}
2426

2527
public float[] Concatenate(List<ResamplerItem> resamplerItems, string tempPath, CancellationTokenSource cancellation) {
@@ -39,7 +41,7 @@ public float[] Concatenate(List<ResamplerItem> resamplerItems, string tempPath,
3941
string batPath = Path.Combine(PathManager.Inst.CachePath, "temp.bat");
4042
lock (tempBatLock) {
4143
using (var stream = File.Open(batPath, FileMode.Create)) {
42-
using (var writer = new StreamWriter(stream, new UTF8Encoding(false))) {
44+
using (var writer = new StreamWriter(stream, osEncoding)) {
4345
WriteSetUp(writer, resamplerItems, tempPath);
4446
for (var i = 0; i < resamplerItems.Count; i++) {
4547
WriteItem(writer, resamplerItems[i], i, resamplerItems.Count);
@@ -62,7 +64,7 @@ void PrepareHelper() {
6264
lock (Renderers.GetCacheLock(tempHelper)) {
6365
if (!File.Exists(tempHelper)) {
6466
using (var stream = File.Open(tempHelper, FileMode.Create)) {
65-
using (var writer = new StreamWriter(stream, new UTF8Encoding(false))) {
67+
using (var writer = new StreamWriter(stream, osEncoding)) {
6668
WriteHelper(writer);
6769
}
6870
}

OpenUtau.Core/Classic/Ini.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ public class IniBlock {
2020
}
2121

2222
public static class Ini {
23-
public static List<IniBlock> ReadBlocks(StreamReader reader, string file, string headerPattern) {
23+
public static List<IniBlock> ReadBlocks(StreamReader reader, string file, string headerPattern, bool trim = true) {
2424
var headerRegex = new Regex(headerPattern);
2525
var blocks = new List<IniBlock>();
2626
var lineNumber = -1;
2727
while (!reader.EndOfStream) {
28-
var line = reader.ReadLine().Trim();
28+
string line;
29+
if (trim) {
30+
line = reader.ReadLine().Trim();
31+
} else {
32+
line = reader.ReadLine();
33+
}
2934
lineNumber++;
3035
if (string.IsNullOrEmpty(line)) {
3136
continue;
@@ -52,7 +57,7 @@ public static List<IniBlock> ReadBlocks(StreamReader reader, string file, string
5257
public static bool TryGetLines(List<IniBlock> blocks, string header, out List<IniLine> lines) {
5358
if (blocks.Any(block => block.header == header)) {
5459
lines = blocks.Find(block => block.header == header).lines;
55-
if (lines == null || lines.Count <= 0) {
60+
if (lines == null || lines.Count < 0) {
5661
return false;
5762
} else {
5863
return true;

OpenUtau.Core/Classic/Presamp.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Linq;
45
using System.Text;
56
using System.Text.RegularExpressions;
67
using OpenUtau.Classic;
@@ -59,24 +60,22 @@ public void ReadPresampIni(string dirPath, Encoding textFileEncoding) {
5960

6061
List<IniBlock> blocks;
6162
using (var reader = new StreamReader(iniPath, textFileEncoding)) {
62-
blocks = Ini.ReadBlocks(reader, iniPath, @"\[\w+\]");
63+
blocks = Ini.ReadBlocks(reader, iniPath, @"\[\w+\]", false);
6364
}
6465

6566
if (Ini.TryGetLines(blocks, "[VOWEL]", out List<IniLine> vowelLines)) {
66-
var list = new List<string>();
67-
vowelLines.ForEach(l => list.Add(l.line));
68-
SetVowels(list);
67+
SetVowels(vowelLines.Select(l => l.line).ToList());
6968
}
7069

7170
if (Ini.TryGetLines(blocks, "[CONSONANT]", out List<IniLine> consonantLines)) {
72-
var list = new List<string>();
73-
consonantLines.ForEach(l => list.Add(l.line));
74-
SetConsonants(list);
71+
SetConsonants(consonantLines.Select(l => l.line).ToList());
7572
}
7673

7774
if (Ini.TryGetLines(blocks, "[PRIORITY]", out List<IniLine> priority)) {
7875
Priorities.Clear();
79-
Priorities.AddRange(priority[0].line.Split(','));
76+
if (priority.Count > 0) {
77+
Priorities.AddRange(priority[0].line.Split(','));
78+
}
8079
}
8180

8281
if (Ini.TryGetLines(blocks, "[REPLACE]", out List<IniLine> replace)) {

OpenUtau.Core/Classic/Ust.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,5 +459,27 @@ public static (List<UNote>, List<UNote>) ParsePlugin(
459459
.ToList();
460460
return (toRemove, toAdd);
461461
}
462+
463+
public static void WriteForSetParam(UProject project, string filePath, List<UOto> otos) {
464+
using (var writer = new StreamWriter(filePath, false, Encoding.GetEncoding("shift_jis"))) {
465+
writer.WriteLine("[#SETTING]");
466+
writer.WriteLine($"Tempo=120");
467+
writer.WriteLine("Tracks=1");
468+
if (project.Saved) {
469+
writer.WriteLine($"Project={project.FilePath.Replace(".ustx", ".ust")}");
470+
}
471+
writer.WriteLine($"VoiceDir={Path.GetDirectoryName(otos[0].File)}");
472+
writer.WriteLine($"CacheDir={PathManager.Inst.CachePath}");
473+
writer.WriteLine("Mode2=True");
474+
475+
for (int i = 0; i < otos.Count; i++) {
476+
UOto oto = otos[i];
477+
writer.WriteLine($"[#{i:D4}]");
478+
writer.WriteLine($"Length=480");
479+
writer.WriteLine($"Lyric={oto.Alias}");
480+
writer.WriteLine($"NoteNum=60");
481+
}
482+
}
483+
}
462484
}
463485
}

OpenUtau.Core/Classic/VoicebankFiles.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ private List<Tuple<string, string>> GetMetaFiles(string source, string sourceTem
5757
Tuple.Create(source + ".pmk", sourceTemp + ".pmk"),
5858
Tuple.Create(source + ".vs4ufrq", sourceTemp + ".vs4ufrq"),
5959
Tuple.Create(noExt + ".rudb", tempNoExt + ".rudb"),
60+
Tuple.Create(noExt + ".sc.npz", tempNoExt + ".sc.npz"),
6061
};
6162
}
6263

OpenUtau.Core/DiffSinger/DiffSingerBasePhonemizer.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ public abstract class DiffSingerBasePhonemizer : MachineLearningPhonemizer
3232

3333
public override void SetSinger(USinger singer) {
3434
this.singer = singer;
35+
if(singer==null){
36+
return;
37+
}
38+
if(singer.Location == null){
39+
Log.Error("Singer location is null");
40+
return;
41+
}
3542
if (File.Exists(Path.Join(singer.Location, "dsdur", "dsconfig.yaml"))) {
3643
rootPath = Path.Combine(singer.Location, "dsdur");
3744
} else {
@@ -74,20 +81,28 @@ public override void SetSinger(USinger singer) {
7481
}
7582

7683
protected virtual IG2p LoadG2p(string rootPath) {
84+
//Each phonemizer has a delicated dictionary name, such as dsdict-en.yaml, dsdict-ru.yaml.
85+
//If this dictionary exists, load it.
86+
//If not, load dsdict.yaml.
7787
var g2ps = new List<IG2p>();
7888
var dictionaryNames = new string[] {GetDictionaryName(), "dsdict.yaml"};
7989
// Load dictionary from singer folder.
90+
G2pDictionary.Builder g2pBuilder = new G2pDictionary.Builder();
8091
foreach(var dictionaryName in dictionaryNames){
8192
string dictionaryPath = Path.Combine(rootPath, dictionaryName);
8293
if (File.Exists(dictionaryPath)) {
8394
try {
84-
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(dictionaryPath)).Build());
95+
g2pBuilder.Load(File.ReadAllText(dictionaryPath)).Build();
8596
} catch (Exception e) {
8697
Log.Error(e, $"Failed to load {dictionaryPath}");
8798
}
8899
break;
89100
}
90101
}
102+
//SP and AP should always be vowel
103+
g2pBuilder.AddSymbol("SP", true);
104+
g2pBuilder.AddSymbol("AP", true);
105+
g2ps.Add(g2pBuilder.Build());
91106
return new G2pFallbacks(g2ps.ToArray());
92107
}
93108

@@ -148,7 +163,7 @@ List<phonemesPerNote> ProcessWord(Note[] notes, string[] symbols){
148163
var isGlide = dsPhonemes.Select(s => g2p.IsGlide(s.Symbol)).ToArray();
149164
var nonExtensionNotes = notes.Where(n=>!IsSyllableVowelExtensionNote(n)).ToArray();
150165
var isStart = new bool[dsPhonemes.Length];
151-
if(!isStart.Any()){
166+
if(isVowel.All(b=>!b)){
152167
isStart[0] = true;
153168
}
154169
for(int i=0; i<dsPhonemes.Length; i++){
@@ -211,6 +226,14 @@ public DiffSingerSpeakerEmbedManager getSpeakerEmbedManager(){
211226
}
212227
return speakerEmbedManager;
213228
}
229+
230+
int PhonemeTokenize(string phoneme){
231+
int result = phonemes.IndexOf(phoneme);
232+
if(result < 0){
233+
throw new Exception($"Phoneme \"{phoneme}\" isn't supported by timing model. Please check {Path.Combine(rootPath, dsConfig.phonemes)}");
234+
}
235+
return result;
236+
}
214237

215238
protected override void ProcessPart(Note[][] phrase) {
216239
float padding = 500f;//Padding time for consonants at the beginning of a sentence, ms
@@ -247,7 +270,7 @@ protected override void ProcessPart(Note[][] phrase) {
247270
//Linguistic Encoder
248271
var tokens = phrasePhonemes
249272
.SelectMany(n => n.Phonemes)
250-
.Select(p => (Int64)phonemes.IndexOf(p.Symbol))
273+
.Select(p => (Int64)PhonemeTokenize(p.Symbol))
251274
.ToArray();
252275
var word_div = phrasePhonemes.Take(phrasePhonemes.Count-1)
253276
.Select(n => (Int64)n.Phonemes.Count)

OpenUtau.Core/DiffSinger/DiffSingerPitch.cs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public DsPitch(string rootPath)
3636
dsConfig = Core.Yaml.DefaultDeserializer.Deserialize<DsConfig>(
3737
File.ReadAllText(Path.Combine(rootPath, "dsconfig.yaml"),
3838
System.Text.Encoding.UTF8));
39+
if(dsConfig.pitch == null){
40+
throw new Exception("This voicebank doesn't contain a pitch model");
41+
}
3942
//Load phonemes list
4043
string phonemesPath = Path.Combine(rootPath, dsConfig.phonemes);
4144
phonemes = File.ReadLines(phonemesPath, Encoding.UTF8).ToList();
@@ -52,17 +55,16 @@ public DsPitch(string rootPath)
5255
}
5356

5457
protected IG2p LoadG2p(string rootPath) {
55-
var g2ps = new List<IG2p>();
5658
// Load dictionary from singer folder.
5759
string file = Path.Combine(rootPath, "dsdict.yaml");
58-
if (File.Exists(file)) {
59-
try {
60-
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(file)).Build());
61-
} catch (Exception e) {
62-
Log.Error(e, $"Failed to load {file}");
63-
}
60+
if(!File.Exists(file)){
61+
throw new Exception($"File not found: {file}");
6462
}
65-
return new G2pFallbacks(g2ps.ToArray());
63+
var g2pBuilder = G2pDictionary.NewBuilder().Load(File.ReadAllText(file));
64+
//SP and AP should always be vowel
65+
g2pBuilder.AddSymbol("SP", true);
66+
g2pBuilder.AddSymbol("AP", true);
67+
return g2pBuilder.Build();
6668
}
6769

6870
public DiffSingerSpeakerEmbedManager getSpeakerEmbedManager(){
@@ -77,6 +79,14 @@ void SetRange<T>(T[] list, T value, int startIndex, int endIndex){
7779
list[i] = value;
7880
}
7981
}
82+
83+
int PhonemeTokenize(string phoneme){
84+
int result = phonemes.IndexOf(phoneme);
85+
if(result < 0){
86+
throw new Exception($"Phoneme \"{phoneme}\" isn't supported by pitch model. Please check {Path.Combine(rootPath, dsConfig.phonemes)}");
87+
}
88+
return result;
89+
}
8090

8191
public RenderPitchResult Process(RenderPhrase phrase){
8292
var startMs = Math.Min(phrase.notes[0].positionMs, phrase.phones[0].positionMs) - headMs;
@@ -86,9 +96,10 @@ public RenderPitchResult Process(RenderPhrase phrase){
8696
//Linguistic Encoder
8797
var linguisticInputs = new List<NamedOnnxValue>();
8898
var tokens = phrase.phones
89-
.Select(p => (Int64)phonemes.IndexOf(p.phoneme))
90-
.Prepend((Int64)phonemes.IndexOf("SP"))
91-
.Append((Int64)phonemes.IndexOf("SP"))
99+
.Select(p => p.phoneme)
100+
.Prepend("SP")
101+
.Append("SP")
102+
.Select(x => (Int64)PhonemeTokenize(x))
92103
.ToArray();
93104
var ph_dur = phrase.phones
94105
.Select(p=>(int)Math.Round(p.endMs/frameMs) - (int)Math.Round(p.positionMs/frameMs))
@@ -104,6 +115,9 @@ public RenderPitchResult Process(RenderPhrase phrase){
104115
var vowelIds = Enumerable.Range(0,phrase.phones.Length)
105116
.Where(i=>g2p.IsVowel(phrase.phones[i].phoneme))
106117
.ToArray();
118+
if(vowelIds.Length == 0){
119+
vowelIds = new int[]{phrase.phones.Length-1};
120+
}
107121
var word_div = vowelIds.Zip(vowelIds.Skip(1),(a,b)=>(Int64)(b-a))
108122
.Prepend(vowelIds[0] + 1)
109123
.Append(phrase.phones.Length - vowelIds[^1] + 1)

0 commit comments

Comments
 (0)