Implement Phase 6R-H advanced narration orchestration

This commit is contained in:
axiomlogicnexus 2026-05-22 03:53:27 +02:00
parent c25a2d7331
commit 3bede5500c
12 changed files with 738 additions and 12 deletions

View file

@ -96,6 +96,42 @@ namespace HyperTwistContractLibraryInternal
return Profile;
}
FHyperTwistNarrationOrchestrationProfile MakeLocalNarrationOrchestrationProfile()
{
FHyperTwistNarrationOrchestrationProfile Profile;
Profile.OrchestrationProfileId = TEXT("local-basic-narration-v1");
Profile.ServiceLaneId = TEXT("voice/local-sidecar");
Profile.ModelBindingId = TEXT("voice-model/local-offline-medium-v1");
Profile.VocoderBindingId = TEXT("vocoder/default-mono-v1");
Profile.DefaultSpeakerProfileId = TEXT("speaker/default");
Profile.bEnableSpeakerSelection = false;
Profile.bEnableLanguageRouting = false;
Profile.bEnableSentenceSplitting = false;
Profile.bEnableSpeakerReferenceConditioning = false;
Profile.bSupportsMultiSpeaker = false;
Profile.bSupportsMultiLingual = false;
Profile.bPreferGpu = false;
return Profile;
}
FHyperTwistNarrationOrchestrationProfile MakeAdvancedNarrationOrchestrationProfile()
{
FHyperTwistNarrationOrchestrationProfile Profile;
Profile.OrchestrationProfileId = TEXT("python-multispeaker-narration-v1");
Profile.ServiceLaneId = TEXT("voice/python-service");
Profile.ModelBindingId = TEXT("voice-model/multilingual-premium-v1");
Profile.VocoderBindingId = TEXT("vocoder/high-fidelity-v1");
Profile.DefaultSpeakerProfileId = TEXT("speaker/companion-guide");
Profile.bEnableSpeakerSelection = true;
Profile.bEnableLanguageRouting = true;
Profile.bEnableSentenceSplitting = true;
Profile.bEnableSpeakerReferenceConditioning = true;
Profile.bSupportsMultiSpeaker = true;
Profile.bSupportsMultiLingual = true;
Profile.bPreferGpu = true;
return Profile;
}
FHyperTwistTrainingSourceAttribution MakeMethodDrillCleanRoomAttribution()
{
FHyperTwistTrainingSourceAttribution Attribution;
@ -826,6 +862,18 @@ TArray<FHyperTwistVoiceProfileSummary> UHyperTwistContractLibrary::MakeSampleVoi
1,
TEXT("de/de_DE/thorsten/medium/MODEL_CARD"),
{TEXT("de-de-thorsten")}
),
HyperTwistContractLibraryInternal::MakeVoiceProfile(
TEXT("multilingual-guide-premium"),
TEXT("Companion Guide"),
TEXT("mul"),
TEXT("mul"),
TEXT("INTL"),
TEXT("Multilingual"),
TEXT("premium"),
32,
TEXT("mul/intl/companion-guide/premium/MODEL_CARD"),
{TEXT("xtts-guide"), TEXT("companion-guide")}
)
};
}
@ -839,18 +887,46 @@ FHyperTwistNarrationSynthesisRequest UHyperTwistContractLibrary::MakeSampleNarra
Request.OutputRouteId = TEXT("companion/narration/local");
Request.VoiceProfileId = TEXT("en_US-lessac-medium");
Request.LanguageCode = TEXT("en_US");
Request.OrchestrationProfile = HyperTwistContractLibraryInternal::MakeLocalNarrationOrchestrationProfile();
Request.ScriptText = TEXT("Good work. Keep your eyes on the next pair.");
Request.SubtitleSeedText = Request.ScriptText;
Request.SpeakerId = 0;
Request.SpeakerProfileId = TEXT("speaker/default");
Request.LengthScale = 1.0f;
Request.NoiseScale = 0.667f;
Request.NoiseW = 0.8f;
Request.SentenceSilenceSeconds = 0.2f;
Request.bSplitSentences = false;
Request.bPreferCuda = false;
Request.AudioEncodingHint = TEXT("wav-pcm16-mono");
return Request;
}
FHyperTwistNarrationSynthesisRequest UHyperTwistContractLibrary::MakeSampleAdvancedNarrationSynthesisRequest()
{
FHyperTwistNarrationSynthesisRequest Request;
Request.RequestId = TEXT("narration_advanced_01");
Request.NarrationContractId = TEXT("embodied-companion-narration");
Request.ServiceLaneId = TEXT("voice/python-service");
Request.OutputRouteId = TEXT("companion/narration/local");
Request.VoiceProfileId = TEXT("multilingual-guide-premium");
Request.LanguageCode = TEXT("fr_FR");
Request.OrchestrationProfile = HyperTwistContractLibraryInternal::MakeAdvancedNarrationOrchestrationProfile();
Request.ScriptText = TEXT("Bien joue. Garde les coins alignes pour le prochain exercice.");
Request.SubtitleSeedText = Request.ScriptText;
Request.SpeakerId = 7;
Request.SpeakerProfileId = TEXT("speaker/companion-guide");
Request.ReferenceVoiceSampleRef = TEXT("mock://voice/companion-guide/reference");
Request.LengthScale = 0.95f;
Request.NoiseScale = 0.5f;
Request.NoiseW = 0.7f;
Request.SentenceSilenceSeconds = 0.12f;
Request.bSplitSentences = true;
Request.bPreferCuda = true;
Request.AudioEncodingHint = TEXT("wav-pcm16-mono");
return Request;
}
FHyperTwistNarrationSynthesisResult UHyperTwistContractLibrary::MakeMockNarrationSynthesisResult()
{
const FHyperTwistNarrationSynthesisRequest Request = MakeSampleNarrationSynthesisRequest();
@ -862,6 +938,12 @@ FHyperTwistNarrationSynthesisResult UHyperTwistContractLibrary::MakeMockNarratio
Result.OutputRouteId = Request.OutputRouteId;
Result.VoiceProfileId = Request.VoiceProfileId;
Result.LanguageCode = Request.LanguageCode;
Result.OrchestrationProfileId = Request.OrchestrationProfile.OrchestrationProfileId;
Result.ModelBindingId = Request.OrchestrationProfile.ModelBindingId;
Result.VocoderBindingId = Request.OrchestrationProfile.VocoderBindingId;
Result.SpeakerProfileId = Request.SpeakerProfileId.IsEmpty()
? Request.OrchestrationProfile.DefaultSpeakerProfileId
: Request.SpeakerProfileId;
Result.SubtitleText = Request.SubtitleSeedText;
Result.AudioEncoding = TEXT("wav-pcm16-mono");
Result.SampleRateHz = 16000;
@ -871,6 +953,11 @@ FHyperTwistNarrationSynthesisResult UHyperTwistContractLibrary::MakeMockNarratio
Result.AppliedNoiseScale = Request.NoiseScale;
Result.AppliedNoiseW = Request.NoiseW;
Result.AppliedSentenceSilenceSeconds = Request.SentenceSilenceSeconds;
Result.bUsedSentenceSplitting = Request.bSplitSentences || Request.OrchestrationProfile.bEnableSentenceSplitting;
Result.bUsedSpeakerSelection =
Request.OrchestrationProfile.bEnableSpeakerSelection && !Result.SpeakerProfileId.IsEmpty();
Result.bUsedSpeakerReferenceConditioning =
Request.OrchestrationProfile.bEnableSpeakerReferenceConditioning && !Request.ReferenceVoiceSampleRef.IsEmpty();
Result.AudioBytes = HyperTwistContractLibraryInternal::MakeSilentMonoPcm16Wav(
Result.SampleRateHz,
Result.DurationMs
@ -889,7 +976,13 @@ FHyperTwistVoiceServiceHealth UHyperTwistContractLibrary::MakeMockVoiceServiceHe
TEXT("offlineNarration"),
TEXT("wavOutput"),
TEXT("subtitleSeed"),
TEXT("localProfileCatalog")
TEXT("localProfileCatalog"),
TEXT("multiSpeakerNarration"),
TEXT("multiLingualNarration"),
TEXT("speakerReferenceConditioning"),
TEXT("sentenceSplitNarration"),
TEXT("modelBindingSelection"),
TEXT("vocoderBindingSelection")
};
for (const FHyperTwistVoiceProfileSummary& Profile : MakeSampleVoiceProfiles())
@ -897,6 +990,19 @@ FHyperTwistVoiceServiceHealth UHyperTwistContractLibrary::MakeMockVoiceServiceHe
Health.SupportedVoiceProfileIds.Add(Profile.VoiceProfileId);
}
Health.SupportedOrchestrationProfiles = {
TEXT("local-basic-narration-v1"),
TEXT("python-multispeaker-narration-v1")
};
Health.SupportedModelBindingIds = {
TEXT("voice-model/local-offline-medium-v1"),
TEXT("voice-model/multilingual-premium-v1")
};
Health.SupportedVocoderBindingIds = {
TEXT("vocoder/default-mono-v1"),
TEXT("vocoder/high-fidelity-v1")
};
Health.bReady = true;
return Health;
}

View file

@ -309,12 +309,23 @@ FHyperTwistNarrationSynthesisResult UHyperTwistHttpVoiceClient::SynthesizeNarrat
Result.OutputRouteId = Request.OutputRouteId;
Result.VoiceProfileId = Request.VoiceProfileId;
Result.LanguageCode = Request.LanguageCode;
Result.OrchestrationProfileId = Request.OrchestrationProfile.OrchestrationProfileId;
Result.ModelBindingId = Request.OrchestrationProfile.ModelBindingId;
Result.VocoderBindingId = Request.OrchestrationProfile.VocoderBindingId;
Result.SpeakerProfileId = Request.SpeakerProfileId.IsEmpty()
? Request.OrchestrationProfile.DefaultSpeakerProfileId
: Request.SpeakerProfileId;
Result.SubtitleText = Request.SubtitleSeedText.IsEmpty() ? Request.ScriptText : Request.SubtitleSeedText;
Result.AudioEncoding = TEXT("wav-pcm16-mono");
Result.AppliedLengthScale = Request.LengthScale;
Result.AppliedNoiseScale = Request.NoiseScale;
Result.AppliedNoiseW = Request.NoiseW;
Result.AppliedSentenceSilenceSeconds = Request.SentenceSilenceSeconds;
Result.bUsedSentenceSplitting = Request.bSplitSentences || Request.OrchestrationProfile.bEnableSentenceSplitting;
Result.bUsedSpeakerSelection =
Request.OrchestrationProfile.bEnableSpeakerSelection && !Result.SpeakerProfileId.IsEmpty();
Result.bUsedSpeakerReferenceConditioning =
Request.OrchestrationProfile.bEnableSpeakerReferenceConditioning && !Request.ReferenceVoiceSampleRef.IsEmpty();
if (!Request.IsStructurallyValid())
{
@ -379,7 +390,13 @@ FHyperTwistVoiceServiceHealth UHyperTwistHttpVoiceClient::GetVoiceServiceHealth(
TEXT("voiceCatalog"),
TEXT("offlineNarration"),
TEXT("wavOutput"),
TEXT("subtitleSeed")
TEXT("subtitleSeed"),
TEXT("multiSpeakerNarration"),
TEXT("multiLingualNarration"),
TEXT("speakerReferenceConditioning"),
TEXT("sentenceSplitNarration"),
TEXT("modelBindingSelection"),
TEXT("vocoderBindingSelection")
};
Health.bReady = false;
@ -412,7 +429,25 @@ FHyperTwistVoiceServiceHealth UHyperTwistHttpVoiceClient::GetVoiceServiceHealth(
TEXT("voiceCatalog"),
TEXT("offlineNarration"),
TEXT("wavOutput"),
TEXT("subtitleSeed")
TEXT("subtitleSeed"),
TEXT("multiSpeakerNarration"),
TEXT("multiLingualNarration"),
TEXT("speakerReferenceConditioning"),
TEXT("sentenceSplitNarration"),
TEXT("modelBindingSelection"),
TEXT("vocoderBindingSelection")
};
Health.SupportedOrchestrationProfiles = {
TEXT("local-basic-narration-v1"),
TEXT("python-multispeaker-narration-v1")
};
Health.SupportedModelBindingIds = {
TEXT("voice-model/local-offline-medium-v1"),
TEXT("voice-model/multilingual-premium-v1")
};
Health.SupportedVocoderBindingIds = {
TEXT("vocoder/default-mono-v1"),
TEXT("vocoder/high-fidelity-v1")
};
Health.bReady = false;
Health.LastError = LastTransportError;
@ -434,6 +469,18 @@ FHyperTwistVoiceServiceHealth UHyperTwistHttpVoiceClient::GetVoiceServiceHealth(
Health.Capabilities.AddUnique(TEXT("offlineNarration"));
Health.Capabilities.AddUnique(TEXT("wavOutput"));
Health.Capabilities.AddUnique(TEXT("subtitleSeed"));
Health.Capabilities.AddUnique(TEXT("multiSpeakerNarration"));
Health.Capabilities.AddUnique(TEXT("multiLingualNarration"));
Health.Capabilities.AddUnique(TEXT("speakerReferenceConditioning"));
Health.Capabilities.AddUnique(TEXT("sentenceSplitNarration"));
Health.Capabilities.AddUnique(TEXT("modelBindingSelection"));
Health.Capabilities.AddUnique(TEXT("vocoderBindingSelection"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("local-basic-narration-v1"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-multispeaker-narration-v1"));
Health.SupportedModelBindingIds.AddUnique(TEXT("voice-model/local-offline-medium-v1"));
Health.SupportedModelBindingIds.AddUnique(TEXT("voice-model/multilingual-premium-v1"));
Health.SupportedVocoderBindingIds.AddUnique(TEXT("vocoder/default-mono-v1"));
Health.SupportedVocoderBindingIds.AddUnique(TEXT("vocoder/high-fidelity-v1"));
LastTransportError.Reset();
Health.LastError.Reset();
return Health;

View file

@ -18,11 +18,22 @@ FHyperTwistNarrationSynthesisResult UHyperTwistMockVoiceClient::SynthesizeNarrat
Result.OutputRouteId = Request.OutputRouteId;
Result.VoiceProfileId = Request.VoiceProfileId;
Result.LanguageCode = Request.LanguageCode;
Result.OrchestrationProfileId = Request.OrchestrationProfile.OrchestrationProfileId;
Result.ModelBindingId = Request.OrchestrationProfile.ModelBindingId;
Result.VocoderBindingId = Request.OrchestrationProfile.VocoderBindingId;
Result.SpeakerProfileId = Request.SpeakerProfileId.IsEmpty()
? Request.OrchestrationProfile.DefaultSpeakerProfileId
: Request.SpeakerProfileId;
Result.SubtitleText = Request.SubtitleSeedText.IsEmpty() ? Request.ScriptText : Request.SubtitleSeedText;
Result.AppliedLengthScale = Request.LengthScale;
Result.AppliedNoiseScale = Request.NoiseScale;
Result.AppliedNoiseW = Request.NoiseW;
Result.AppliedSentenceSilenceSeconds = Request.SentenceSilenceSeconds;
Result.bUsedSentenceSplitting = Request.bSplitSentences || Request.OrchestrationProfile.bEnableSentenceSplitting;
Result.bUsedSpeakerSelection =
Request.OrchestrationProfile.bEnableSpeakerSelection && !Result.SpeakerProfileId.IsEmpty();
Result.bUsedSpeakerReferenceConditioning =
Request.OrchestrationProfile.bEnableSpeakerReferenceConditioning && !Request.ReferenceVoiceSampleRef.IsEmpty();
if (!Request.IsStructurallyValid())
{
@ -33,6 +44,12 @@ FHyperTwistNarrationSynthesisResult UHyperTwistMockVoiceClient::SynthesizeNarrat
Result.OutputRouteId = Request.OutputRouteId;
Result.VoiceProfileId = Request.VoiceProfileId;
Result.LanguageCode = Request.LanguageCode;
Result.OrchestrationProfileId = Request.OrchestrationProfile.OrchestrationProfileId;
Result.ModelBindingId = Request.OrchestrationProfile.ModelBindingId;
Result.VocoderBindingId = Request.OrchestrationProfile.VocoderBindingId;
Result.SpeakerProfileId = Request.SpeakerProfileId.IsEmpty()
? Request.OrchestrationProfile.DefaultSpeakerProfileId
: Request.SpeakerProfileId;
Result.Warnings.Add(TEXT("narration-request-invalid"));
return Result;
}
@ -64,5 +81,13 @@ FHyperTwistVoiceServiceHealth UHyperTwistMockVoiceClient::GetVoiceServiceHealth(
Health.Capabilities.AddUnique(TEXT("voiceCatalog"));
Health.Capabilities.AddUnique(TEXT("wavOutput"));
Health.Capabilities.AddUnique(TEXT("subtitleSeed"));
Health.Capabilities.AddUnique(TEXT("multiSpeakerNarration"));
Health.Capabilities.AddUnique(TEXT("multiLingualNarration"));
Health.Capabilities.AddUnique(TEXT("speakerReferenceConditioning"));
Health.Capabilities.AddUnique(TEXT("sentenceSplitNarration"));
Health.Capabilities.AddUnique(TEXT("modelBindingSelection"));
Health.Capabilities.AddUnique(TEXT("vocoderBindingSelection"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("local-basic-narration-v1"));
Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-multispeaker-narration-v1"));
return Health;
}

View file

@ -72,6 +72,9 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
static FHyperTwistNarrationSynthesisRequest MakeSampleNarrationSynthesisRequest();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
static FHyperTwistNarrationSynthesisRequest MakeSampleAdvancedNarrationSynthesisRequest();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
static FHyperTwistNarrationSynthesisResult MakeMockNarrationSynthesisResult();

View file

@ -1006,6 +1006,57 @@ struct FHyperTwistVoiceProfileCatalog
}
};
USTRUCT(BlueprintType)
struct FHyperTwistNarrationOrchestrationProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString OrchestrationProfileId = TEXT("local-basic-narration-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ServiceLaneId = TEXT("voice/local-sidecar");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ModelBindingId = TEXT("voice-model/local-offline-medium-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString VocoderBindingId = TEXT("vocoder/default-mono-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DefaultSpeakerProfileId = TEXT("speaker/default");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bEnableSpeakerSelection = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bEnableLanguageRouting = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bEnableSentenceSplitting = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bEnableSpeakerReferenceConditioning = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsMultiSpeaker = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsMultiLingual = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreferGpu = false;
bool IsStructurallyValid() const
{
return !OrchestrationProfileId.IsEmpty()
&& !ServiceLaneId.IsEmpty()
&& !ModelBindingId.IsEmpty()
&& !VocoderBindingId.IsEmpty()
&& !DefaultSpeakerProfileId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistNarrationSynthesisRequest
{
@ -1029,6 +1080,9 @@ struct FHyperTwistNarrationSynthesisRequest
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LanguageCode = TEXT("en_US");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistNarrationOrchestrationProfile OrchestrationProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ScriptText;
@ -1038,6 +1092,12 @@ struct FHyperTwistNarrationSynthesisRequest
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SpeakerId = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SpeakerProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ReferenceVoiceSampleRef;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float LengthScale = 1.0f;
@ -1050,6 +1110,9 @@ struct FHyperTwistNarrationSynthesisRequest
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float SentenceSilenceSeconds = 0.2f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSplitSentences = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreferCuda = false;
@ -1064,6 +1127,8 @@ struct FHyperTwistNarrationSynthesisRequest
&& !OutputRouteId.IsEmpty()
&& !VoiceProfileId.IsEmpty()
&& !LanguageCode.IsEmpty()
&& OrchestrationProfile.IsStructurallyValid()
&& ServiceLaneId.Equals(OrchestrationProfile.ServiceLaneId, ESearchCase::CaseSensitive)
&& !ScriptText.IsEmpty()
&& !AudioEncodingHint.IsEmpty()
&& LengthScale > 0.0f
@ -1096,6 +1161,18 @@ struct FHyperTwistNarrationSynthesisResult
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LanguageCode = TEXT("en_US");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString OrchestrationProfileId = TEXT("local-basic-narration-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ModelBindingId = TEXT("voice-model/local-offline-medium-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString VocoderBindingId = TEXT("vocoder/default-mono-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SpeakerProfileId = TEXT("speaker/default");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SubtitleText;
@ -1123,6 +1200,15 @@ struct FHyperTwistNarrationSynthesisResult
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float AppliedSentenceSilenceSeconds = 0.2f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bUsedSentenceSplitting = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bUsedSpeakerSelection = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bUsedSpeakerReferenceConditioning = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<uint8> AudioBytes;
@ -1137,6 +1223,9 @@ struct FHyperTwistNarrationSynthesisResult
&& !OutputRouteId.IsEmpty()
&& !VoiceProfileId.IsEmpty()
&& !LanguageCode.IsEmpty()
&& !OrchestrationProfileId.IsEmpty()
&& !ModelBindingId.IsEmpty()
&& !VocoderBindingId.IsEmpty()
&& !SubtitleText.IsEmpty()
&& !AudioEncoding.IsEmpty()
&& SampleRateHz > 0
@ -1170,6 +1259,15 @@ struct FHyperTwistVoiceServiceHealth
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> SupportedVoiceProfileIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> SupportedOrchestrationProfiles;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> SupportedModelBindingIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> SupportedVocoderBindingIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastError;

View file

@ -0,0 +1,189 @@
// Copyright HyperTwist, Inc. All Rights Reserved.
#include "Misc/AutomationTest.h"
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HyperTwistRecognition/HyperTwistVoiceClient.h"
#include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h"
#if WITH_AUTOMATION_TESTS
namespace HyperTwistCoquiPhase6RHTestInternal
{
UHyperTwistMockVoiceClient* MakeMockVoiceClient()
{
return NewObject<UHyperTwistMockVoiceClient>(GetTransientPackage());
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistCoquiPhase6RHAdvancedNarrationRequestTest,
"HyperTwist.Permissive.Coqui.Phase6R.H.AdvancedNarrationRequest",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistCoquiPhase6RHAdvancedNarrationRequestTest::RunTest(const FString& Parameters)
{
FHyperTwistTrainingCompanionNarrationContract NarrationContract;
TestTrue(
TEXT("The bundled embodied companion narration contract must stay available for Phase 6R-H."),
UHyperTwistTrainingRuntimeLibrary::TryGetBundledEmbodiedCompanionNarrationContract(
TEXT("embodied-companion-narration"),
NarrationContract
)
);
TestTrue(TEXT("The bundled narration contract must remain structurally valid."), NarrationContract.IsStructurallyValid());
UHyperTwistMockVoiceClient* VoiceClient = HyperTwistCoquiPhase6RHTestInternal::MakeMockVoiceClient();
TestNotNull(TEXT("The mock voice client must be constructed for Phase 6R-H."), VoiceClient);
if (VoiceClient == nullptr)
{
return false;
}
const TArray<FHyperTwistVoiceProfileSummary> Profiles = VoiceClient->ListVoiceProfiles();
TestTrue(TEXT("The retained advanced narration slice must expose a multilingual guide profile."), Profiles.ContainsByPredicate(
[](const FHyperTwistVoiceProfileSummary& Profile)
{
return Profile.VoiceProfileId == TEXT("multilingual-guide-premium")
&& Profile.NumSpeakers >= 2;
}
));
const FHyperTwistNarrationSynthesisRequest Request =
UHyperTwistContractLibrary::MakeSampleAdvancedNarrationSynthesisRequest();
TestTrue(TEXT("The advanced narration request must be structurally valid."), Request.IsStructurallyValid());
TestEqual(
TEXT("The advanced request must stay on the embodied companion narration contract."),
Request.NarrationContractId,
TEXT("embodied-companion-narration")
);
TestEqual(
TEXT("The advanced request must move to the Python voice service lane."),
Request.ServiceLaneId,
TEXT("voice/python-service")
);
TestEqual(
TEXT("The advanced orchestration profile must be bounded to the retained multispeaker narration slice."),
Request.OrchestrationProfile.OrchestrationProfileId,
TEXT("python-multispeaker-narration-v1")
);
TestEqual(
TEXT("The advanced request must carry the normalized narration model binding."),
Request.OrchestrationProfile.ModelBindingId,
TEXT("voice-model/multilingual-premium-v1")
);
TestEqual(
TEXT("The advanced request must carry the normalized vocoder binding."),
Request.OrchestrationProfile.VocoderBindingId,
TEXT("vocoder/high-fidelity-v1")
);
TestTrue(TEXT("The advanced request must enable speaker selection."), Request.OrchestrationProfile.bEnableSpeakerSelection);
TestTrue(TEXT("The advanced request must enable language routing."), Request.OrchestrationProfile.bEnableLanguageRouting);
TestTrue(TEXT("The advanced request must enable sentence splitting."), Request.bSplitSentences);
TestTrue(
TEXT("The advanced request must retain a reference-voice sample ref without making voice assets part of the packet."),
!Request.ReferenceVoiceSampleRef.IsEmpty()
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistCoquiPhase6RHAdvancedNarrationResultTest,
"HyperTwist.Permissive.Coqui.Phase6R.H.AdvancedNarrationResult",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistCoquiPhase6RHAdvancedNarrationResultTest::RunTest(const FString& Parameters)
{
UHyperTwistMockVoiceClient* VoiceClient = HyperTwistCoquiPhase6RHTestInternal::MakeMockVoiceClient();
TestNotNull(TEXT("The mock voice client must be constructed for Phase 6R-H."), VoiceClient);
if (VoiceClient == nullptr)
{
return false;
}
const FHyperTwistNarrationSynthesisRequest Request =
UHyperTwistContractLibrary::MakeSampleAdvancedNarrationSynthesisRequest();
const FHyperTwistNarrationSynthesisResult Result = VoiceClient->SynthesizeNarration(Request);
TestTrue(TEXT("The advanced narration synthesis result must stay structurally valid."), Result.IsStructurallyValid());
TestEqual(TEXT("The advanced result must preserve the request id."), Result.RequestId, Request.RequestId);
TestEqual(
TEXT("The advanced result must preserve the advanced orchestration profile id."),
Result.OrchestrationProfileId,
Request.OrchestrationProfile.OrchestrationProfileId
);
TestEqual(TEXT("The advanced result must stay on the Python voice service lane."), Result.ServiceLaneId, TEXT("voice/python-service"));
TestEqual(
TEXT("The advanced result must preserve the normalized narration model binding."),
Result.ModelBindingId,
Request.OrchestrationProfile.ModelBindingId
);
TestEqual(
TEXT("The advanced result must preserve the normalized vocoder binding."),
Result.VocoderBindingId,
Request.OrchestrationProfile.VocoderBindingId
);
TestEqual(
TEXT("The advanced result must preserve the selected speaker profile."),
Result.SpeakerProfileId,
Request.SpeakerProfileId
);
TestTrue(TEXT("The advanced result must report sentence splitting usage."), Result.bUsedSentenceSplitting);
TestTrue(TEXT("The advanced result must report speaker selection usage."), Result.bUsedSpeakerSelection);
TestTrue(
TEXT("The advanced result must report reference-voice conditioning usage."),
Result.bUsedSpeakerReferenceConditioning
);
TestEqual(
TEXT("The advanced result must preserve subtitle text."),
Result.SubtitleText,
Request.SubtitleSeedText
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistCoquiPhase6RHServiceHealthTest,
"HyperTwist.Permissive.Coqui.Phase6R.H.ServiceHealth",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistCoquiPhase6RHServiceHealthTest::RunTest(const FString& Parameters)
{
UHyperTwistMockVoiceClient* VoiceClient = HyperTwistCoquiPhase6RHTestInternal::MakeMockVoiceClient();
TestNotNull(TEXT("The mock voice client must be constructed for Phase 6R-H."), VoiceClient);
if (VoiceClient == nullptr)
{
return false;
}
const FHyperTwistVoiceServiceHealth Health = VoiceClient->GetVoiceServiceHealth();
TestTrue(TEXT("The advanced voice service health must be structurally valid."), Health.IsStructurallyValid());
TestTrue(TEXT("The advanced voice service health must report ready."), Health.bReady);
TestTrue(TEXT("The advanced voice service must expose multispeaker narration capability."), Health.Capabilities.Contains(TEXT("multiSpeakerNarration")));
TestTrue(TEXT("The advanced voice service must expose multilingual narration capability."), Health.Capabilities.Contains(TEXT("multiLingualNarration")));
TestTrue(
TEXT("The advanced voice service must expose speaker-reference conditioning capability."),
Health.Capabilities.Contains(TEXT("speakerReferenceConditioning"))
);
TestTrue(TEXT("The advanced voice service must expose sentence-split narration capability."), Health.Capabilities.Contains(TEXT("sentenceSplitNarration")));
TestTrue(TEXT("The advanced voice service must expose model-binding selection capability."), Health.Capabilities.Contains(TEXT("modelBindingSelection")));
TestTrue(TEXT("The advanced voice service must expose vocoder-binding selection capability."), Health.Capabilities.Contains(TEXT("vocoderBindingSelection")));
TestTrue(
TEXT("The advanced voice service must advertise the retained advanced orchestration profile."),
Health.SupportedOrchestrationProfiles.Contains(TEXT("python-multispeaker-narration-v1"))
);
TestTrue(
TEXT("The advanced voice service must advertise the normalized premium narration model binding."),
Health.SupportedModelBindingIds.Contains(TEXT("voice-model/multilingual-premium-v1"))
);
TestTrue(
TEXT("The advanced voice service must advertise the normalized high-fidelity vocoder binding."),
Health.SupportedVocoderBindingIds.Contains(TEXT("vocoder/high-fidelity-v1"))
);
return true;
}
#endif

View file

@ -1312,6 +1312,7 @@ Decision date:
- `2026-04-24`
- refreshed on `2026-05-13`
- refreshed on `2026-05-22`
Current licensing judgment:
@ -1343,6 +1344,15 @@ Approved working posture:
- do not collapse code and model-weight licensing into one bucket
- track each chosen voice model or downloadable payload separately at implementation time
- prefer a bounded speech sidecar or service seam so the product can swap or isolate model choices later without entangling the core coaching logic
- the first bounded advanced narration slice is now landed:
- advanced narration orchestration profile
- normalized model/vocoder binding metadata
- multilingual routing and speaker-selection flags
- reference-speaker conditioning flag
- bounded mock and HTTP voice-client seam support for the richer narration profile
- advanced voice service-health capability exposure
- `coqui-ai/TTS` remains a bounded donor, not the broad voice-platform owner
- keep downloadable voice/model review separate from the code-license judgment
### `rhasspy/piper`

View file

@ -0,0 +1,144 @@
# HyperTwist Phase 6R-H coqui-ai/TTS advanced narration implementation packet
Created on `2026-05-22`
## Status
- first-party HyperTwist packet
- bounded permissive `Phase 6R-H` implementation slice
## Purpose
This packet lands the first narrower bounded slice from the retained `coqui-ai/TTS` voice-output
row.
The landed slice is:
- first-party advanced narration orchestration profile above the existing embodied companion
narration lane
It is not:
- a broad TTS platform packet
- a downloadable voice-asset or model-shipping packet
- a provider-profile or BYOK packet
- a voice-conversion packet
- a viseme / gesture runtime packet
## Current authority basis
This implementation packet stands on:
- `docs/HYPERTWIST_PHASE_0R_PACKET_0R_A_EVALUATION_2026-05-12.md`
- `docs/HYPERTWIST_PHASE_2R_PACKET_2R_A_OWNERSHIP_AND_ACCEPTANCE_CONTRACT_2026-05-13.md`
- `docs/REPO_LICENSE_TRACKING.md`
- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md`
- `docs/arch/HYPERTWIST_PHASE6R_H_COQUI_TTS_ADVANCED_NARRATION_PREPARATION_PACKET_2026-05-22.md`
The retained owner remains:
- `coqui-ai/TTS`
The top-level provider and companion contract owner does not change here:
- first-party HyperTwist remains the provider-neutral and companion-contract owner
- this packet keeps `coqui-ai/TTS` bounded to the narrower `A1 / R2 / F2` advanced narration
orchestration slice
## Landed scope
The current code now owns a retained advanced narration orchestration contract through:
- retained recognition contract types for:
- narration orchestration profile
- advanced narration request metadata
- advanced narration result metadata
- advanced voice service health metadata
- sample voice profiles, basic and advanced narration request shaping, narration result shaping, and
voice service health outputs in:
- `UHyperTwistContractLibrary`
- bounded voice client seam support in:
- `UHyperTwistMockVoiceClient`
- `UHyperTwistHttpVoiceClient`
- focused automation coverage in:
- `HyperTwistCoquiPhase6RHAdvancedNarrationContractTest.cpp`
## Why this is still intentionally bounded
This packet lands a richer voice-output donor seam, but it does not widen into the neighboring
retained families.
Still deferred:
- downloadable voice/model review
- bundled voice asset shipping
- voice conversion
- broad playback runtime ownership
- viseme / gesture runtime integration
- provider-profile and BYOK custody
- broad assistant-platform scope
## Validation
Build validation:
- `Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE`
Focused automation validation:
- `Automation RunTests HyperTwist.Permissive.Coqui.Phase6R.H`
Regression automation validation:
- `Automation RunTests HyperTwist.Permissive.Piper.Phase6R.G`
- `Automation RunTests HyperTwist.Permissive.FasterWhisper.Phase6R.F`
- `Automation RunTests HyperTwist.Permissive.WhisperCpp.Phase6R.E`
- `Automation RunTests HyperTwist.CleanRoom.CubeDesk`
Expected covered tests:
- `AdvancedNarrationRequest`
- `AdvancedNarrationResult`
- `ServiceHealth`
- `NarrationRequest`
- `SynthesisResult`
- `SessionConfig`
- `TranscriptMetadata`
- `TranscriptResult`
- existing `CubeDesk` clean-room regression suite
## Queue effect
This packet consumes the current `Phase 6R-H` implementation slice.
`coqui-ai/TTS` remains only partially incorporated:
- landed now:
- advanced narration orchestration profile
- normalized model/vocoder binding metadata
- multilingual routing and speaker-selection flags
- reference-speaker conditioning flag
- bounded mock and HTTP voice-client seam support for the richer narration profile
- advanced voice service-health capability exposure
- still deferred:
- downloadable voice/model review
- bundled voice asset shipping
- voice conversion
- broad playback runtime ownership
- viseme / gesture runtime integration
- broad assistant-platform scope
The next clean move is:
- source-backed `Phase 6R-I` `roice3/Magic120Cell` preparation/control pass
Keep the future sequencing guards visible:
- speech-input / voice sidecar set
- keep code-license judgments separate from model, voice, and payload-license review
- `coqui-ai/TTS`
- keep the landed slice bounded to orchestration metadata rather than broad voice-platform
ownership
- `roice3/Magic120Cell`
- keep the next widening slice bounded to one retained hyper-engine family rather than treating
the row as a generic non-Euclidean platform by default

View file

@ -0,0 +1,79 @@
# HyperTwist Phase 6R-H coqui-ai/TTS advanced narration preparation packet
Created on `2026-05-22`
## Status
- first-party HyperTwist packet
- source-backed permissive `Phase 6R-H` preparation/control slice
## Purpose
This packet narrows the retained `coqui-ai/TTS` row before implementation.
The retained first slice is:
- advanced narration orchestration profile above the already landed embodied companion narration lane
It is not:
- a broad TTS platform packet
- a downloadable voice-asset or model-shipping packet
- a provider-profile or BYOK packet
- a voice-conversion packet
- a viseme, gesture, or avatar-runtime widening packet
## Source-backed basis
Primary donor surfaces inspected:
- `README.md`
- `docs/source/inference.md`
- `TTS/server/README.md`
- `TTS/api.py`
## Narrowing decision
The strongest bounded donor seam is not raw audio output by itself. That lighter local sidecar lane
already landed through `rhasspy/piper`.
The smallest stronger `coqui-ai/TTS` seam is:
- richer narration orchestration metadata
- normalized model/vocoder binding selection
- speaker-selection and multilingual routing flags
- optional speaker-reference conditioning
- sentence-splitting policy above the same first-party narration contract
The first bounded implementation packet should therefore land only:
- first-party advanced narration orchestration profile types
- bounded request/result metadata for model/vocoder/speaker/reference selection
- mock and HTTP voice-client seam support for the bounded advanced narration profile
- focused automation around the advanced narration request/result/health contract
## Explicit deferrals
Still deferred after this control pass:
- downloadable voice/model review
- bundled voice asset shipping
- voice conversion
- broad playback runtime ownership
- viseme / gesture runtime integration
- provider-profile and BYOK custody
- broad assistant-platform scope
## Queue effect
This preparation packet authorizes one bounded permissive implementation packet:
- `Phase 6R-H` advanced narration orchestration profile
After that packet, move the queue head to:
- source-backed `Phase 6R-I` `roice3/Magic120Cell` preparation/control pass
Keep the legal guard visible:
- `coqui-ai/TTS` code-license judgment stays separate from model, voice, and payload review

View file

@ -80,10 +80,10 @@ Use these as the current governing docs:
The next bounded move is now:
1. source-backed `Phase 6R-H` `coqui-ai/TTS` preparation/control pass
2. keep it behind the landed `whisper.cpp`, `faster-whisper`, and `piper` bounded slices
3. narrow the first widening slice to one richer bounded voice-output seam
4. keep voice-asset and payload review separate from the code-license judgment
1. source-backed `Phase 6R-I` `roice3/Magic120Cell` preparation/control pass
2. keep it after the landed `whisper.cpp`, `faster-whisper`, `piper`, and `coqui-ai/TTS` bounded slices
3. narrow the first widening slice to one bounded hyper-engine topology/runtime-anchor seam
4. keep later non-Euclidean shell widening separate from the code-license judgment and retained row ownership
## Memory-specific sequencing rule

View file

@ -183,6 +183,7 @@ repo.
| Provider-backed speech session health and transcript envelopes | Implemented now | first-party speech client surfaces | Current first-party speech seam. |
| Python transcription-service orchestration profile | Implemented now | landed `faster-whisper` packet | Current bounded Python STT orchestration truth above the existing speech session boundary. |
| Local narration sidecar seam | Implemented now | landed `piper` packet | Current bounded offline narration contract, voice catalog, and local sidecar truth. |
| Advanced narration orchestration profile | Implemented now | landed `coqui-ai/TTS` packet | Current bounded richer narration profile above the existing companion narration contract. |
| Broad voice output / narration sidecars | Deep-source grounded retained | `piper`, `coqui-ai/TTS` retained remainder | Keep downloadable voice/model review separate from the code-license judgment. |
| Broad live microphone shell and downloadable payload shipping | Deep-source grounded retained | speech stack retained remainder | Explicitly deferred. |

View file

@ -78,8 +78,13 @@ Canonical discovery surfaces for roadmap interpretation:
in current code
- `rhasspy/piper` remains partially incorporated; downloadable voice/model review, bundled voice
asset shipping, and broad voice-platform ownership stay deferred
- the current next bounded move is a source-backed `Phase 6R-H` `coqui-ai/TTS`
preparation/control pass for a richer bounded voice-output seam
- the generic source-backed `Phase 6R-H` `coqui-ai/TTS` control pass is now consumed
- the bounded permissive `Phase 6R-H` `coqui-ai/TTS` advanced narration orchestration packet is now
landed in current code
- `coqui-ai/TTS` remains partially incorporated; downloadable voice/model review, bundled voice
asset shipping, voice conversion, and broad voice-platform ownership stay deferred
- the current next bounded move is a source-backed `Phase 6R-I` `roice3/Magic120Cell`
preparation/control pass for one bounded hyper-engine widening slice
- the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than
waiting on another first-party packet
@ -141,8 +146,8 @@ Current routing truth:
- active non-live implementation-board rows: `35`
- retained benchmark, oracle, or clean-room-later rows outside the active implementation board: `9`
The next bounded move is a source-backed `Phase 6R-H`
`coqui-ai/TTS` preparation/control pass.
The next bounded move is a source-backed `Phase 6R-I`
`roice3/Magic120Cell` preparation/control pass.
Queue interpretation after that packet:
@ -225,13 +230,32 @@ Queue interpretation after that packet:
- rich playback runtime ownership
- viseme / gesture runtime integration
- broad assistant-platform scope
- `coqui-ai/TTS` is now landed as a partially incorporated row rather than a generic future
placeholder:
- landed:
- advanced narration orchestration profile
- normalized model/vocoder binding metadata
- multilingual routing and speaker-selection flags
- reference-speaker conditioning flag
- bounded mock and HTTP voice-client seam support for the richer narration profile
- advanced voice service-health capability exposure
- still deferred:
- downloadable voice/model review
- bundled voice asset shipping
- voice conversion
- broad playback runtime ownership
- viseme / gesture runtime integration
- broad assistant-platform scope
- the next queue shape is now:
- `coqui-ai/TTS`
- `roice3/Magic120Cell`
- keep the speech-input / voice sidecar legal sequencing guard visible:
- keep code-license judgments separate from model, voice, and payload-license review
- keep the provider-neutral speech-lane guard visible:
- first-party HyperTwist owns the top-level provider/session contract
- `whisper.cpp` and `faster-whisper` may win narrower donor slices without inheriting that lane
- keep the voice-output guard visible:
- `coqui-ai/TTS` stays bounded to advanced narration orchestration metadata rather than broad
voice-platform ownership
- keep the optional `Phase 3R-G` browser comparison lane deferred unless the landed primary browser
spatial stack exposes a real gap