Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
40171cb
Add HTS label support infrastructure
rokujyushi May 18, 2026
7572f6e
Addition of NEUTRINO Support
rokujyushi May 18, 2026
92c1f73
Fix expected values for test assertions
rokujyushi May 18, 2026
ebfe4e4
Addressing Copilot AI review comments
rokujyushi May 18, 2026
53cac3f
Addressing Copilot AI review comments
rokujyushi May 18, 2026
54f77a3
Changing from a design that shares logic in the base class to impleme…
rokujyushi May 18, 2026
9eec4e8
Merge remote-tracking branch 'origin/hts-label' into neutrino
rokujyushi May 18, 2026
ea527ac
Unixで実行可能なファイルパスに修正
rokujyushi May 19, 2026
6b02dd4
Change the voicebank version information to be retrieved from the Tom…
rokujyushi May 19, 2026
c4d7790
Merge branch 'neutrino' of https://github.com/rokujyushi/OpenUtau int…
rokujyushi May 19, 2026
9b49026
Added processing to consider toneShift in pitch calculation
rokujyushi May 20, 2026
6e55aed
Refactoring of log improvements and environment variable settings
rokujyushi May 20, 2026
930782a
Unifying Path Handling
rokujyushi May 20, 2026
536ccd5
Address review comments from Copilot
rokujyushi May 20, 2026
60ca037
Address review comments from Copilot
rokujyushi May 20, 2026
b08c1de
Merged the fix for hts-label.
rokujyushi May 20, 2026
06dd804
Improvements to Vowel Extension Note Processing and Timing Calculations
rokujyushi May 21, 2026
317c910
Changed the naming convention for cache files to use hash values.
rokujyushi May 22, 2026
2e001a0
Improved path handling for command line arguments
rokujyushi May 22, 2026
dffeec9
Exposing monoLabel and adding customization processing
rokujyushi May 22, 2026
f4426db
Merge branch 'stakira:master' into neutrino
rokujyushi May 22, 2026
27ac2aa
In accordance with the NEUTRINO update, I have set the minimum versio…
rokujyushi May 30, 2026
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
2 changes: 2 additions & 0 deletions OpenUtau.Core/Classic/ClassicSingerLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ static USinger AdjustSingerType(Voicebank v) {
return new Core.DiffSinger.DiffSingerSinger(v) as USinger;
case USingerType.Voicevox:
return new Core.Voicevox.VoicevoxSinger(v) as USinger;
case USingerType.Neutrino:
return new Core.Neutrino.NeutrinoSinger(v) as USinger;
default:
return new ClassicSinger(v) as USinger;
}
Expand Down
677 changes: 677 additions & 0 deletions OpenUtau.Core/Hts/HTSLabelPhonemizer.cs

Large diffs are not rendered by default.

507 changes: 507 additions & 0 deletions OpenUtau.Core/Hts/HTSLabelRenderer.cs

Large diffs are not rendered by default.

175 changes: 175 additions & 0 deletions OpenUtau.Core/Neutrino/NeutrinoLabelPhonemizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenUtau.Api;
using OpenUtau.Core.Hts;
using OpenUtau.Core.Ustx;
using OpenUtau.Core.Util;
using Serilog;

namespace OpenUtau.Core.Neutrino {
[Phonemizer("Neutrino Label Phonemizer", "NEUTRINO")]
public class NeutrinoLabelPhonemizer : HTSLabelPhonemizer {
string NeutrinoExe = string.Empty;
Comment thread
rokujyushi marked this conversation as resolved.
string NeutrinoClientExe = string.Empty;
string NeutrinoServerExe = string.Empty;
string NsfExe = string.Empty;
string WorldExe = string.Empty;

protected new NeutrinoSinger singer;

List<string> macronLyrics = new List<string>();

public override void SetSinger(USinger singer) {
this.singer = singer as NeutrinoSinger;
if (this.singer == null) {
return;
}
lang = "JPN";//TODO: use singer.language
string confPath = "japanese.utf_8.conf";
tablePath = "japanese.utf_8.table";
string basePath = Path.Join(PathManager.Inst.DependencyPath, "NEUTRINO");
if (!Directory.Exists(basePath)) {
if (this.singer.singerVersion.StartsWith("v2.7")) {
basePath = Path.Join(PathManager.Inst.DependencyPath, "NEUTRINO_v27");
} else if (this.singer.singerVersion.StartsWith("v3") && !this.singer.singerVersion.StartsWith("v3.1")) {
basePath = Path.Join(PathManager.Inst.DependencyPath, "NEUTRINO_v3");
}
}
//Load Dictionary
try {
phoneDict.Clear();
LoadDict(Path.Join(basePath, "settings", "dic", confPath), singer.TextFileEncoding);
LoadDict(Path.Join(basePath, "settings", "dic", tablePath), singer.TextFileEncoding);
// Lyrics often handled in OpenUtau
phoneDict.Add("R", new string[] { "pau" });
phoneDict.Add("-", new string[] { "pau" });
phoneDict.Add("SP", new string[] { "pau" });
phoneDict.Add("AP", new string[] { "br" });
g2p = this.LoadG2p();
} catch (Exception e) {
Log.Error(e, $"failed to load dictionary from {tablePath}");
return;
}
if (OS.IsWindows()) {
NeutrinoExe = Path.Join(basePath, @"bin", "NEUTRINO.exe");
NeutrinoClientExe = Path.Join(basePath, @"bin", "neutrino_client.exe");
NeutrinoServerExe = Path.Join(basePath, @"bin", "neutrino_server.exe");
} else if (OS.IsMacOS() || OS.IsLinux()) {
NeutrinoExe = Path.Join(basePath, @"bin", "NEUTRINO");
} else {
throw new NotSupportedException("Platform not supported.");
}
NeutrinoServerLauncher.EnsureStarted(NeutrinoServerExe);
}
protected IG2p LoadG2p() {
var g2ps = new List<IG2p>();
var builder = G2pDictionary.NewBuilder();
vowels.AddRange(phoneDict["VOWELS"]);
breaks.AddRange(phoneDict["BREAK"]);
pauses.AddRange(phoneDict["PAUSES"]);
silences.AddRange(phoneDict["SILENCES"]);
consonants.AddRange(phoneDict["PHONEME_CL"]);
macronLyrics.AddRange(phoneDict["MACRON"]);
foreach (var dict in phoneDict.Values) {
foreach (var phoneme in dict) {
if (!consonants.Contains(phoneme) && !vowels.Contains(phoneme) &&
!breaks.Contains(phoneme) && !pauses.Contains(phoneme) &&
!silences.Contains(phoneme)) {
consonants.Add(phoneme);
}
if (!consonants.Contains(phoneme)) {
builder.AddSymbol(phoneme, true);
} else {
builder.AddSymbol(phoneme, false);
}
}
}
foreach (var entry in phoneDict.Keys) {
builder.AddEntry(entry, phoneDict[entry]);
foreach (var reduction in phoneDict["VOWEL_REDUCTION"]) {
var phonemes = phoneDict[entry].Except(vowels).ToList();
if (phonemes.Count == 0) continue;
builder.AddEntry(entry + reduction, phonemes);
}
foreach (var macron in phoneDict["MACRON"]) {
var addPhonemes = phoneDict[entry].Where(x => vowels.Contains(x)).ToList();
if (addPhonemes.Count == 0) continue;
var phonemes = phoneDict[entry].ToList();
phonemes.AddRange(addPhonemes);
builder.AddEntry(entry + macron, phonemes);
macronLyrics.Add(entry + macron);
}
}
g2ps.Add(builder.Build());
return new G2pFallbacks(g2ps.ToArray());
}

protected override Note[][] PhraseAdjustments(Note[][] phrese) {
for (int i = 0; i < phrese.Length; i++) {
var lyric = phrese[i][0].lyric;
if (phoneDict["MACRON"].Contains(lyric) && (i > 0)) {
if (g2p.IsValidSymbol(lyric)) {
var vowel = g2p.Query(phrese[i - 1][0].lyric).FirstOrDefault(phoneme => vowels.Contains(phoneme));
if (!string.IsNullOrEmpty(vowel)) {
phrese[i][0].lyric = vowel;
}
}
}
}
return phrese;
}

protected override HTSNote CustomHTSNoteContext(HTSNote htsNote, Note note) {
var fixs = GetPrefixAndSuffix(note);
if (!htsNote.isRest && !htsNote.isSlur) {
htsNote.langDependent = "0"; // no macron
if (macronLyrics.Contains(note.lyric)) {
htsNote.langDependent = "1"; // macron
}
}
return htsNote;
}

protected override HTSPhoneme[] CustomHTSPhonemeContext(HTSPhoneme[] htsPhonemes, Note[] notes) {
var fixs = GetPrefixAndSuffix(notes[0]);
foreach (var htsPhoneme in htsPhonemes) {
htsPhoneme.flag1 = "00"; // NEUTRINO Default.
}
return htsPhonemes;
}

protected override void SendScore(Note[][] phrase) {
if (this.singer.singerVersion == null) {
return;
}
if (File.Exists(fullScorePath) && !File.Exists(monoTimingPath)) {
var voicebankNameHash = $"{this.singer.voicebankNameHash:x16}";
string f0Path = Path.Join(htstmpPath, $"{voicebankNameHash}_tmp.f0");
string melspecPath = Path.Join(htstmpPath, $"{voicebankNameHash}_tmp.melspec");
string wavPath = Path.Join(htstmpPath, $"{voicebankNameHash}_tmp.wav");
string modelDir = this.singer.Location + "/";
var attr = phrase[0][0].phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default;
int toneShift = attr.toneShift;
int numThreads = Preferences.Default.NumRenderThreads;
string ArgParam = string.Empty;
if (this.singer.singerVersion.StartsWith("v2.7")) {
ArgParam = $"{fullScorePath} {monoTimingPath} {f0Path} {melspecPath} {modelDir} -a -k {toneShift} -d 3 -n 1 -p {numThreads} -m -t";
} else if (this.singer.singerVersion.StartsWith("v3") && !this.singer.singerVersion.StartsWith("v3.1")) {
//TODO: -S support model
ArgParam = $"\"{fullScorePath}\" \"{monoTimingPath}\" \"{f0Path}\" \"{melspecPath}\" \"{wavPath}\" \"{modelDir}\" --skip-f0 --skip-melspec --skip-wav -k {toneShift} -m -t";
} else {
Log.Error($"Unsupported NEUTRINO version: {this.singer.singerVersion}");
return;
}
Log.Information($"NEUTRINO timing args: {ArgParam}");
if (File.Exists(NeutrinoClientExe)) {
ProcessRunner.Run(NeutrinoClientExe, ArgParam, Log.Logger);
} else {
ProcessRunner.Run(NeutrinoExe, ArgParam, Log.Logger);
}
}
}
}
}
Loading
Loading