Implement Phase 6R-G local narration sidecar contract
This commit is contained in:
parent
5d834b56f9
commit
c25a2d7331
14 changed files with 1443 additions and 19 deletions
|
|
@ -24,3 +24,11 @@ TranscribeUtterancePath=/speech/utterance
|
|||
CloseSessionPath=/speech/session/close
|
||||
HealthPath=/speech/health
|
||||
RequestTimeoutSeconds=5.0
|
||||
|
||||
[/Script/UnrealHyperTwist.HyperTwistHttpVoiceClient]
|
||||
ProviderLabel=local-http-sidecar
|
||||
ServiceBaseUrl=http://127.0.0.1:8766
|
||||
ProfilesPath=/voice/profiles
|
||||
SynthesizePath=/voice/narrate
|
||||
HealthPath=/voice/health
|
||||
RequestTimeoutSeconds=5.0
|
||||
|
|
|
|||
|
|
@ -24,6 +24,78 @@ namespace HyperTwistContractLibraryInternal
|
|||
return FJsonObjectConverter::JsonObjectStringToUStruct(Json, &OutValue, 0, 0);
|
||||
}
|
||||
|
||||
void AppendLe16(TArray<uint8>& Buffer, const uint16 Value)
|
||||
{
|
||||
Buffer.Add(static_cast<uint8>(Value & 0xff));
|
||||
Buffer.Add(static_cast<uint8>((Value >> 8) & 0xff));
|
||||
}
|
||||
|
||||
void AppendLe32(TArray<uint8>& Buffer, const uint32 Value)
|
||||
{
|
||||
Buffer.Add(static_cast<uint8>(Value & 0xff));
|
||||
Buffer.Add(static_cast<uint8>((Value >> 8) & 0xff));
|
||||
Buffer.Add(static_cast<uint8>((Value >> 16) & 0xff));
|
||||
Buffer.Add(static_cast<uint8>((Value >> 24) & 0xff));
|
||||
}
|
||||
|
||||
TArray<uint8> MakeSilentMonoPcm16Wav(const int32 SampleRateHz, const int32 DurationMs)
|
||||
{
|
||||
const int32 ClampedSampleRate = FMath::Max(SampleRateHz, 8000);
|
||||
const int32 ClampedDurationMs = FMath::Max(DurationMs, 100);
|
||||
const uint32 SampleCount = static_cast<uint32>((static_cast<int64>(ClampedSampleRate) * ClampedDurationMs) / 1000);
|
||||
const uint16 ChannelCount = 1;
|
||||
const uint16 BitsPerSample = 16;
|
||||
const uint16 BlockAlign = static_cast<uint16>(ChannelCount * (BitsPerSample / 8));
|
||||
const uint32 ByteRate = static_cast<uint32>(ClampedSampleRate) * BlockAlign;
|
||||
const uint32 DataSize = SampleCount * BlockAlign;
|
||||
const uint32 RiffSize = 36 + DataSize;
|
||||
|
||||
TArray<uint8> Buffer;
|
||||
Buffer.Reserve(static_cast<int32>(44 + DataSize));
|
||||
Buffer.Append(reinterpret_cast<const uint8*>("RIFF"), 4);
|
||||
AppendLe32(Buffer, RiffSize);
|
||||
Buffer.Append(reinterpret_cast<const uint8*>("WAVE"), 4);
|
||||
Buffer.Append(reinterpret_cast<const uint8*>("fmt "), 4);
|
||||
AppendLe32(Buffer, 16);
|
||||
AppendLe16(Buffer, 1);
|
||||
AppendLe16(Buffer, ChannelCount);
|
||||
AppendLe32(Buffer, static_cast<uint32>(ClampedSampleRate));
|
||||
AppendLe32(Buffer, ByteRate);
|
||||
AppendLe16(Buffer, BlockAlign);
|
||||
AppendLe16(Buffer, BitsPerSample);
|
||||
Buffer.Append(reinterpret_cast<const uint8*>("data"), 4);
|
||||
AppendLe32(Buffer, DataSize);
|
||||
Buffer.AddZeroed(static_cast<int32>(DataSize));
|
||||
return Buffer;
|
||||
}
|
||||
|
||||
FHyperTwistVoiceProfileSummary MakeVoiceProfile(
|
||||
const TCHAR* VoiceProfileId,
|
||||
const TCHAR* VoiceName,
|
||||
const TCHAR* LanguageCode,
|
||||
const TCHAR* LanguageFamily,
|
||||
const TCHAR* RegionCode,
|
||||
const TCHAR* LanguageNameEnglish,
|
||||
const TCHAR* Quality,
|
||||
const int32 NumSpeakers,
|
||||
const TCHAR* ModelCardRelativePath,
|
||||
const TArray<FString>& Aliases
|
||||
)
|
||||
{
|
||||
FHyperTwistVoiceProfileSummary Profile;
|
||||
Profile.VoiceProfileId = VoiceProfileId;
|
||||
Profile.VoiceName = VoiceName;
|
||||
Profile.LanguageCode = LanguageCode;
|
||||
Profile.LanguageFamily = LanguageFamily;
|
||||
Profile.RegionCode = RegionCode;
|
||||
Profile.LanguageNameEnglish = LanguageNameEnglish;
|
||||
Profile.Quality = Quality;
|
||||
Profile.NumSpeakers = NumSpeakers;
|
||||
Profile.ModelCardRelativePath = ModelCardRelativePath;
|
||||
Profile.Aliases = Aliases;
|
||||
return Profile;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSourceAttribution MakeMethodDrillCleanRoomAttribution()
|
||||
{
|
||||
FHyperTwistTrainingSourceAttribution Attribution;
|
||||
|
|
@ -716,6 +788,119 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService
|
|||
return Health;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistVoiceProfileSummary> UHyperTwistContractLibrary::MakeSampleVoiceProfiles()
|
||||
{
|
||||
return {
|
||||
HyperTwistContractLibraryInternal::MakeVoiceProfile(
|
||||
TEXT("en_US-lessac-medium"),
|
||||
TEXT("Lessac"),
|
||||
TEXT("en_US"),
|
||||
TEXT("en"),
|
||||
TEXT("US"),
|
||||
TEXT("English"),
|
||||
TEXT("medium"),
|
||||
1,
|
||||
TEXT("en/en_US/lessac/medium/MODEL_CARD"),
|
||||
{TEXT("en-us-lessac")}
|
||||
),
|
||||
HyperTwistContractLibraryInternal::MakeVoiceProfile(
|
||||
TEXT("en_GB-alan-medium"),
|
||||
TEXT("Alan"),
|
||||
TEXT("en_GB"),
|
||||
TEXT("en"),
|
||||
TEXT("GB"),
|
||||
TEXT("English"),
|
||||
TEXT("medium"),
|
||||
1,
|
||||
TEXT("en/en_GB/alan/medium/MODEL_CARD"),
|
||||
{TEXT("en-gb-alan")}
|
||||
),
|
||||
HyperTwistContractLibraryInternal::MakeVoiceProfile(
|
||||
TEXT("de_DE-thorsten-medium"),
|
||||
TEXT("Thorsten"),
|
||||
TEXT("de_DE"),
|
||||
TEXT("de"),
|
||||
TEXT("DE"),
|
||||
TEXT("German"),
|
||||
TEXT("medium"),
|
||||
1,
|
||||
TEXT("de/de_DE/thorsten/medium/MODEL_CARD"),
|
||||
{TEXT("de-de-thorsten")}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
FHyperTwistNarrationSynthesisRequest UHyperTwistContractLibrary::MakeSampleNarrationSynthesisRequest()
|
||||
{
|
||||
FHyperTwistNarrationSynthesisRequest Request;
|
||||
Request.RequestId = TEXT("narration_01");
|
||||
Request.NarrationContractId = TEXT("embodied-companion-narration");
|
||||
Request.ServiceLaneId = TEXT("voice/local-sidecar");
|
||||
Request.OutputRouteId = TEXT("companion/narration/local");
|
||||
Request.VoiceProfileId = TEXT("en_US-lessac-medium");
|
||||
Request.LanguageCode = TEXT("en_US");
|
||||
Request.ScriptText = TEXT("Good work. Keep your eyes on the next pair.");
|
||||
Request.SubtitleSeedText = Request.ScriptText;
|
||||
Request.SpeakerId = 0;
|
||||
Request.LengthScale = 1.0f;
|
||||
Request.NoiseScale = 0.667f;
|
||||
Request.NoiseW = 0.8f;
|
||||
Request.SentenceSilenceSeconds = 0.2f;
|
||||
Request.bPreferCuda = false;
|
||||
Request.AudioEncodingHint = TEXT("wav-pcm16-mono");
|
||||
return Request;
|
||||
}
|
||||
|
||||
FHyperTwistNarrationSynthesisResult UHyperTwistContractLibrary::MakeMockNarrationSynthesisResult()
|
||||
{
|
||||
const FHyperTwistNarrationSynthesisRequest Request = MakeSampleNarrationSynthesisRequest();
|
||||
|
||||
FHyperTwistNarrationSynthesisResult Result;
|
||||
Result.RequestId = Request.RequestId;
|
||||
Result.NarrationContractId = Request.NarrationContractId;
|
||||
Result.ServiceLaneId = Request.ServiceLaneId;
|
||||
Result.OutputRouteId = Request.OutputRouteId;
|
||||
Result.VoiceProfileId = Request.VoiceProfileId;
|
||||
Result.LanguageCode = Request.LanguageCode;
|
||||
Result.SubtitleText = Request.SubtitleSeedText;
|
||||
Result.AudioEncoding = TEXT("wav-pcm16-mono");
|
||||
Result.SampleRateHz = 16000;
|
||||
Result.ChannelCount = 1;
|
||||
Result.DurationMs = 1400;
|
||||
Result.AppliedLengthScale = Request.LengthScale;
|
||||
Result.AppliedNoiseScale = Request.NoiseScale;
|
||||
Result.AppliedNoiseW = Request.NoiseW;
|
||||
Result.AppliedSentenceSilenceSeconds = Request.SentenceSilenceSeconds;
|
||||
Result.AudioBytes = HyperTwistContractLibraryInternal::MakeSilentMonoPcm16Wav(
|
||||
Result.SampleRateHz,
|
||||
Result.DurationMs
|
||||
);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistVoiceServiceHealth UHyperTwistContractLibrary::MakeMockVoiceServiceHealth()
|
||||
{
|
||||
FHyperTwistVoiceServiceHealth Health;
|
||||
Health.ProviderLabel = TEXT("mock");
|
||||
Health.ServiceVersion = TEXT("mock-voice/v1");
|
||||
Health.ServiceEndpoint = TEXT("in-process://mock");
|
||||
Health.Capabilities = {
|
||||
TEXT("voiceCatalog"),
|
||||
TEXT("offlineNarration"),
|
||||
TEXT("wavOutput"),
|
||||
TEXT("subtitleSeed"),
|
||||
TEXT("localProfileCatalog")
|
||||
};
|
||||
|
||||
for (const FHyperTwistVoiceProfileSummary& Profile : MakeSampleVoiceProfiles())
|
||||
{
|
||||
Health.SupportedVoiceProfileIds.Add(Profile.VoiceProfileId);
|
||||
}
|
||||
|
||||
Health.bReady = true;
|
||||
return Health;
|
||||
}
|
||||
|
||||
FHyperTwistContentPack UHyperTwistContractLibrary::MakeSampleAlgTrainerContentPack()
|
||||
{
|
||||
return UHyperTwistTrainingCatalogLibrary::MakeAlgTrainerStarterContentPack();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,440 @@
|
|||
#include "HyperTwistRecognition/HyperTwistVoiceClient.h"
|
||||
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Http.h"
|
||||
#include "HttpManager.h"
|
||||
#include "HttpModule.h"
|
||||
#include "Interfaces/IHttpRequest.h"
|
||||
#include "Interfaces/IHttpResponse.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
|
||||
namespace HyperTwistHttpVoiceClientInternal
|
||||
{
|
||||
template <typename TStruct>
|
||||
bool SerializeStruct(const TStruct& Value, FString& OutJson)
|
||||
{
|
||||
return FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, OutJson, 0, 0);
|
||||
}
|
||||
|
||||
template <typename TStruct>
|
||||
bool DeserializeStruct(const FString& Json, TStruct& OutValue)
|
||||
{
|
||||
return !Json.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Json, &OutValue, 0, 0);
|
||||
}
|
||||
|
||||
FString BuildUrl(const FString& BaseUrl, const FString& Path)
|
||||
{
|
||||
if (BaseUrl.IsEmpty())
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
|
||||
const FString TrimmedBase = BaseUrl.EndsWith(TEXT("/")) ? BaseUrl.LeftChop(1) : BaseUrl;
|
||||
if (Path.IsEmpty())
|
||||
{
|
||||
return TrimmedBase;
|
||||
}
|
||||
|
||||
const FString TrimmedPath = Path.StartsWith(TEXT("/")) ? Path.RightChop(1) : Path;
|
||||
return FString::Printf(TEXT("%s/%s"), *TrimmedBase, *TrimmedPath);
|
||||
}
|
||||
|
||||
struct FHttpResponseState
|
||||
{
|
||||
bool bCompleted = false;
|
||||
bool bSucceeded = false;
|
||||
int32 StatusCode = 0;
|
||||
FString ResponseBody;
|
||||
TArray<uint8> ResponseBytes;
|
||||
FString Error;
|
||||
};
|
||||
|
||||
bool ExecuteRequest(
|
||||
const UHyperTwistHttpVoiceClient& Client,
|
||||
const FString& Verb,
|
||||
const FString& Url,
|
||||
const FString& AcceptHeader,
|
||||
const FString& ContentType,
|
||||
const FString& RequestBody,
|
||||
FHttpResponseState& OutResponse)
|
||||
{
|
||||
OutResponse = FHttpResponseState();
|
||||
|
||||
if (Url.IsEmpty())
|
||||
{
|
||||
OutResponse.Error = TEXT("service-base-url-missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
const TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
|
||||
Request->SetURL(Url);
|
||||
Request->SetVerb(Verb);
|
||||
Request->SetHeader(TEXT("Accept"), AcceptHeader);
|
||||
if (!ContentType.IsEmpty())
|
||||
{
|
||||
Request->SetHeader(TEXT("Content-Type"), ContentType);
|
||||
}
|
||||
|
||||
if (!Client.AuthorizationToken.IsEmpty())
|
||||
{
|
||||
Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Bearer %s"), *Client.AuthorizationToken));
|
||||
}
|
||||
|
||||
if (!RequestBody.IsEmpty())
|
||||
{
|
||||
Request->SetContentAsString(RequestBody);
|
||||
}
|
||||
|
||||
Request->OnProcessRequestComplete().BindLambda(
|
||||
[&OutResponse](FHttpRequestPtr, FHttpResponsePtr Response, bool bWasSuccessful)
|
||||
{
|
||||
OutResponse.bCompleted = true;
|
||||
|
||||
if (Response.IsValid())
|
||||
{
|
||||
OutResponse.StatusCode = Response->GetResponseCode();
|
||||
OutResponse.ResponseBody = Response->GetContentAsString();
|
||||
OutResponse.ResponseBytes = Response->GetContent();
|
||||
}
|
||||
|
||||
if (!bWasSuccessful)
|
||||
{
|
||||
OutResponse.Error = TEXT("request-failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Response.IsValid())
|
||||
{
|
||||
OutResponse.Error = TEXT("response-missing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EHttpResponseCodes::IsOk(OutResponse.StatusCode))
|
||||
{
|
||||
OutResponse.Error = FString::Printf(TEXT("http-%d"), OutResponse.StatusCode);
|
||||
return;
|
||||
}
|
||||
|
||||
OutResponse.bSucceeded = true;
|
||||
});
|
||||
|
||||
if (!Request->ProcessRequest())
|
||||
{
|
||||
OutResponse.Error = TEXT("request-start-failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
const double TimeoutSeconds = FMath::Max(static_cast<double>(Client.RequestTimeoutSeconds), 0.1);
|
||||
const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds;
|
||||
while (!OutResponse.bCompleted && FPlatformTime::Seconds() < Deadline)
|
||||
{
|
||||
FHttpModule::Get().GetHttpManager().Tick(0.01f);
|
||||
FPlatformProcess::Sleep(0.01f);
|
||||
}
|
||||
|
||||
if (!OutResponse.bCompleted)
|
||||
{
|
||||
Request->CancelRequest();
|
||||
OutResponse.Error = TEXT("request-timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
return OutResponse.bSucceeded;
|
||||
}
|
||||
|
||||
bool ReadLe16(const TArray<uint8>& Bytes, const int32 Offset, uint16& OutValue)
|
||||
{
|
||||
if (Offset < 0 || Offset + 1 >= Bytes.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutValue = static_cast<uint16>(Bytes[Offset]) | (static_cast<uint16>(Bytes[Offset + 1]) << 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadLe32(const TArray<uint8>& Bytes, const int32 Offset, uint32& OutValue)
|
||||
{
|
||||
if (Offset < 0 || Offset + 3 >= Bytes.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutValue = static_cast<uint32>(Bytes[Offset])
|
||||
| (static_cast<uint32>(Bytes[Offset + 1]) << 8)
|
||||
| (static_cast<uint32>(Bytes[Offset + 2]) << 16)
|
||||
| (static_cast<uint32>(Bytes[Offset + 3]) << 24);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseWavMetadata(
|
||||
const TArray<uint8>& Bytes,
|
||||
int32& OutSampleRateHz,
|
||||
int32& OutChannelCount,
|
||||
int32& OutDurationMs)
|
||||
{
|
||||
OutSampleRateHz = 0;
|
||||
OutChannelCount = 0;
|
||||
OutDurationMs = 0;
|
||||
|
||||
if (Bytes.Num() < 44
|
||||
|| Bytes[0] != 'R'
|
||||
|| Bytes[1] != 'I'
|
||||
|| Bytes[2] != 'F'
|
||||
|| Bytes[3] != 'F'
|
||||
|| Bytes[8] != 'W'
|
||||
|| Bytes[9] != 'A'
|
||||
|| Bytes[10] != 'V'
|
||||
|| Bytes[11] != 'E')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 Offset = 12;
|
||||
uint16 Channels = 0;
|
||||
uint32 SampleRate = 0;
|
||||
uint16 BitsPerSample = 0;
|
||||
uint32 DataSize = 0;
|
||||
|
||||
while (Offset + 8 <= Bytes.Num())
|
||||
{
|
||||
const ANSICHAR ChunkIdChars[5] = {
|
||||
static_cast<ANSICHAR>(Bytes[Offset]),
|
||||
static_cast<ANSICHAR>(Bytes[Offset + 1]),
|
||||
static_cast<ANSICHAR>(Bytes[Offset + 2]),
|
||||
static_cast<ANSICHAR>(Bytes[Offset + 3]),
|
||||
0
|
||||
};
|
||||
const FString ChunkId(ANSI_TO_TCHAR(ChunkIdChars));
|
||||
|
||||
uint32 ChunkSize = 0;
|
||||
if (!ReadLe32(Bytes, Offset + 4, ChunkSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 ChunkDataOffset = Offset + 8;
|
||||
if (ChunkDataOffset + static_cast<int32>(ChunkSize) > Bytes.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ChunkId == TEXT("fmt "))
|
||||
{
|
||||
uint16 AudioFormat = 0;
|
||||
if (!ReadLe16(Bytes, ChunkDataOffset, AudioFormat)
|
||||
|| !ReadLe16(Bytes, ChunkDataOffset + 2, Channels)
|
||||
|| !ReadLe32(Bytes, ChunkDataOffset + 4, SampleRate)
|
||||
|| !ReadLe16(Bytes, ChunkDataOffset + 14, BitsPerSample))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioFormat != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (ChunkId == TEXT("data"))
|
||||
{
|
||||
DataSize = ChunkSize;
|
||||
}
|
||||
|
||||
Offset = ChunkDataOffset + static_cast<int32>(ChunkSize);
|
||||
if ((ChunkSize % 2U) != 0U)
|
||||
{
|
||||
++Offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (Channels <= 0 || SampleRate <= 0 || BitsPerSample <= 0 || DataSize <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 BytesPerSampleFrame = Channels * (BitsPerSample / 8);
|
||||
if (BytesPerSampleFrame <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double FrameCount = static_cast<double>(DataSize) / static_cast<double>(BytesPerSampleFrame);
|
||||
OutSampleRateHz = static_cast<int32>(SampleRate);
|
||||
OutChannelCount = static_cast<int32>(Channels);
|
||||
OutDurationMs = FMath::Max(
|
||||
0,
|
||||
FMath::RoundToInt(static_cast<float>((FrameCount * 1000.0) / static_cast<double>(SampleRate)))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FHyperTwistVoiceProfileSummary> UHyperTwistHttpVoiceClient::ListVoiceProfiles() const
|
||||
{
|
||||
HyperTwistHttpVoiceClientInternal::FHttpResponseState Response;
|
||||
if (!HyperTwistHttpVoiceClientInternal::ExecuteRequest(
|
||||
*this,
|
||||
TEXT("GET"),
|
||||
HyperTwistHttpVoiceClientInternal::BuildUrl(ServiceBaseUrl, ProfilesPath),
|
||||
TEXT("application/json"),
|
||||
FString(),
|
||||
FString(),
|
||||
Response))
|
||||
{
|
||||
LastTransportError = Response.Error;
|
||||
return {};
|
||||
}
|
||||
|
||||
FHyperTwistVoiceProfileCatalog Catalog;
|
||||
if (!HyperTwistHttpVoiceClientInternal::DeserializeStruct(Response.ResponseBody, Catalog)
|
||||
|| !Catalog.IsStructurallyValid())
|
||||
{
|
||||
LastTransportError = TEXT("invalid-provider-response");
|
||||
return {};
|
||||
}
|
||||
|
||||
LastTransportError.Reset();
|
||||
return Catalog.Profiles;
|
||||
}
|
||||
|
||||
FHyperTwistNarrationSynthesisResult UHyperTwistHttpVoiceClient::SynthesizeNarration(
|
||||
const FHyperTwistNarrationSynthesisRequest& Request)
|
||||
{
|
||||
FHyperTwistNarrationSynthesisResult Result;
|
||||
Result.RequestId = Request.RequestId;
|
||||
Result.NarrationContractId = Request.NarrationContractId;
|
||||
Result.ServiceLaneId = Request.ServiceLaneId;
|
||||
Result.OutputRouteId = Request.OutputRouteId;
|
||||
Result.VoiceProfileId = Request.VoiceProfileId;
|
||||
Result.LanguageCode = Request.LanguageCode;
|
||||
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;
|
||||
|
||||
if (!Request.IsStructurallyValid())
|
||||
{
|
||||
Result.Warnings.Add(TEXT("narration-request-invalid"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FString RequestJson;
|
||||
if (!HyperTwistHttpVoiceClientInternal::SerializeStruct(Request, RequestJson))
|
||||
{
|
||||
LastTransportError = TEXT("request-serialize-failed");
|
||||
Result.Warnings.Add(LastTransportError);
|
||||
return Result;
|
||||
}
|
||||
|
||||
HyperTwistHttpVoiceClientInternal::FHttpResponseState Response;
|
||||
if (!HyperTwistHttpVoiceClientInternal::ExecuteRequest(
|
||||
*this,
|
||||
TEXT("POST"),
|
||||
HyperTwistHttpVoiceClientInternal::BuildUrl(ServiceBaseUrl, SynthesizePath),
|
||||
TEXT("audio/wav"),
|
||||
TEXT("application/json"),
|
||||
RequestJson,
|
||||
Response))
|
||||
{
|
||||
LastTransportError = Response.Error;
|
||||
Result.Warnings.Add(LastTransportError);
|
||||
return Result;
|
||||
}
|
||||
|
||||
int32 SampleRateHz = 0;
|
||||
int32 ChannelCount = 0;
|
||||
int32 DurationMs = 0;
|
||||
if (!HyperTwistHttpVoiceClientInternal::ParseWavMetadata(
|
||||
Response.ResponseBytes,
|
||||
SampleRateHz,
|
||||
ChannelCount,
|
||||
DurationMs))
|
||||
{
|
||||
LastTransportError = TEXT("invalid-provider-audio");
|
||||
Result.Warnings.Add(LastTransportError);
|
||||
return Result;
|
||||
}
|
||||
|
||||
Result.SampleRateHz = SampleRateHz;
|
||||
Result.ChannelCount = ChannelCount;
|
||||
Result.DurationMs = DurationMs;
|
||||
Result.AudioBytes = Response.ResponseBytes;
|
||||
LastTransportError.Reset();
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistVoiceServiceHealth UHyperTwistHttpVoiceClient::GetVoiceServiceHealth() const
|
||||
{
|
||||
FHyperTwistVoiceServiceHealth Health;
|
||||
Health.ProviderLabel = ProviderLabel;
|
||||
Health.ServiceVersion = TEXT("provider-unavailable/v1");
|
||||
Health.ServiceEndpoint = ServiceBaseUrl;
|
||||
Health.Capabilities = {
|
||||
TEXT("transport:http"),
|
||||
TEXT("provider-backed-voice"),
|
||||
TEXT("voiceCatalog"),
|
||||
TEXT("offlineNarration"),
|
||||
TEXT("wavOutput"),
|
||||
TEXT("subtitleSeed")
|
||||
};
|
||||
Health.bReady = false;
|
||||
|
||||
HyperTwistHttpVoiceClientInternal::FHttpResponseState Response;
|
||||
if (!HyperTwistHttpVoiceClientInternal::ExecuteRequest(
|
||||
*this,
|
||||
TEXT("GET"),
|
||||
HyperTwistHttpVoiceClientInternal::BuildUrl(ServiceBaseUrl, HealthPath),
|
||||
TEXT("application/json"),
|
||||
FString(),
|
||||
FString(),
|
||||
Response))
|
||||
{
|
||||
LastTransportError = Response.Error;
|
||||
Health.LastError = LastTransportError;
|
||||
return Health;
|
||||
}
|
||||
|
||||
if (!HyperTwistHttpVoiceClientInternal::DeserializeStruct(Response.ResponseBody, Health)
|
||||
|| !Health.IsStructurallyValid())
|
||||
{
|
||||
LastTransportError = TEXT("invalid-provider-response");
|
||||
Health = FHyperTwistVoiceServiceHealth();
|
||||
Health.ProviderLabel = ProviderLabel;
|
||||
Health.ServiceVersion = TEXT("provider-unavailable/v1");
|
||||
Health.ServiceEndpoint = ServiceBaseUrl;
|
||||
Health.Capabilities = {
|
||||
TEXT("transport:http"),
|
||||
TEXT("provider-backed-voice"),
|
||||
TEXT("voiceCatalog"),
|
||||
TEXT("offlineNarration"),
|
||||
TEXT("wavOutput"),
|
||||
TEXT("subtitleSeed")
|
||||
};
|
||||
Health.bReady = false;
|
||||
Health.LastError = LastTransportError;
|
||||
return Health;
|
||||
}
|
||||
|
||||
if (Health.ProviderLabel.IsEmpty())
|
||||
{
|
||||
Health.ProviderLabel = ProviderLabel;
|
||||
}
|
||||
if (Health.ServiceEndpoint.IsEmpty())
|
||||
{
|
||||
Health.ServiceEndpoint = ServiceBaseUrl;
|
||||
}
|
||||
|
||||
Health.Capabilities.AddUnique(TEXT("transport:http"));
|
||||
Health.Capabilities.AddUnique(TEXT("provider-backed-voice"));
|
||||
Health.Capabilities.AddUnique(TEXT("voiceCatalog"));
|
||||
Health.Capabilities.AddUnique(TEXT("offlineNarration"));
|
||||
Health.Capabilities.AddUnique(TEXT("wavOutput"));
|
||||
Health.Capabilities.AddUnique(TEXT("subtitleSeed"));
|
||||
LastTransportError.Reset();
|
||||
Health.LastError.Reset();
|
||||
return Health;
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#include "HyperTwistRecognition/HyperTwistVoiceClient.h"
|
||||
|
||||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
|
||||
TArray<FHyperTwistVoiceProfileSummary> UHyperTwistMockVoiceClient::ListVoiceProfiles() const
|
||||
{
|
||||
return UHyperTwistContractLibrary::MakeSampleVoiceProfiles();
|
||||
}
|
||||
|
||||
FHyperTwistNarrationSynthesisResult UHyperTwistMockVoiceClient::SynthesizeNarration(
|
||||
const FHyperTwistNarrationSynthesisRequest& Request
|
||||
)
|
||||
{
|
||||
FHyperTwistNarrationSynthesisResult Result = UHyperTwistContractLibrary::MakeMockNarrationSynthesisResult();
|
||||
Result.RequestId = Request.RequestId;
|
||||
Result.NarrationContractId = Request.NarrationContractId;
|
||||
Result.ServiceLaneId = Request.ServiceLaneId;
|
||||
Result.OutputRouteId = Request.OutputRouteId;
|
||||
Result.VoiceProfileId = Request.VoiceProfileId;
|
||||
Result.LanguageCode = Request.LanguageCode;
|
||||
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;
|
||||
|
||||
if (!Request.IsStructurallyValid())
|
||||
{
|
||||
Result = FHyperTwistNarrationSynthesisResult();
|
||||
Result.RequestId = Request.RequestId;
|
||||
Result.NarrationContractId = Request.NarrationContractId;
|
||||
Result.ServiceLaneId = Request.ServiceLaneId;
|
||||
Result.OutputRouteId = Request.OutputRouteId;
|
||||
Result.VoiceProfileId = Request.VoiceProfileId;
|
||||
Result.LanguageCode = Request.LanguageCode;
|
||||
Result.Warnings.Add(TEXT("narration-request-invalid"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool bKnownProfile = false;
|
||||
for (const FHyperTwistVoiceProfileSummary& Profile : UHyperTwistContractLibrary::MakeSampleVoiceProfiles())
|
||||
{
|
||||
if (Profile.VoiceProfileId.Equals(Request.VoiceProfileId, ESearchCase::IgnoreCase))
|
||||
{
|
||||
bKnownProfile = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bKnownProfile)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("voice-profile-not-found"));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistVoiceServiceHealth UHyperTwistMockVoiceClient::GetVoiceServiceHealth() const
|
||||
{
|
||||
FHyperTwistVoiceServiceHealth Health = UHyperTwistContractLibrary::MakeMockVoiceServiceHealth();
|
||||
Health.ProviderLabel = TEXT("mock");
|
||||
Health.ServiceEndpoint = TEXT("in-process://mock");
|
||||
Health.Capabilities.AddUnique(TEXT("offlineNarration"));
|
||||
Health.Capabilities.AddUnique(TEXT("voiceCatalog"));
|
||||
Health.Capabilities.AddUnique(TEXT("wavOutput"));
|
||||
Health.Capabilities.AddUnique(TEXT("subtitleSeed"));
|
||||
return Health;
|
||||
}
|
||||
|
|
@ -66,6 +66,18 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistSpeechServiceHealth MakeMockSpeechServiceHealth();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static TArray<FHyperTwistVoiceProfileSummary> MakeSampleVoiceProfiles();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistNarrationSynthesisRequest MakeSampleNarrationSynthesisRequest();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistNarrationSynthesisResult MakeMockNarrationSynthesisResult();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistVoiceServiceHealth MakeMockVoiceServiceHealth();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistContentPack MakeSampleAlgTrainerContentPack();
|
||||
|
||||
|
|
|
|||
|
|
@ -930,3 +930,254 @@ struct FHyperTwistSpeechServiceHealth
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bReady = true;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistVoiceProfileSummary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VoiceProfileId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VoiceName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LanguageCode = TEXT("en_US");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LanguageFamily = TEXT("en");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RegionCode = TEXT("US");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LanguageNameEnglish = TEXT("English");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Quality = TEXT("medium");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 NumSpeakers = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ModelCardRelativePath;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Aliases;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !VoiceProfileId.IsEmpty()
|
||||
&& !VoiceName.IsEmpty()
|
||||
&& !LanguageCode.IsEmpty()
|
||||
&& !LanguageFamily.IsEmpty()
|
||||
&& !RegionCode.IsEmpty()
|
||||
&& !LanguageNameEnglish.IsEmpty()
|
||||
&& !Quality.IsEmpty()
|
||||
&& NumSpeakers > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistVoiceProfileCatalog
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistVoiceProfileSummary> Profiles;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
if (Profiles.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const FHyperTwistVoiceProfileSummary& Profile : Profiles)
|
||||
{
|
||||
if (!Profile.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistNarrationSynthesisRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RequestId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NarrationContractId = TEXT("embodied-companion-narration");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ServiceLaneId = TEXT("voice/local-sidecar");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString OutputRouteId = TEXT("companion/narration/local");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VoiceProfileId = TEXT("en_US-lessac-medium");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LanguageCode = TEXT("en_US");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ScriptText;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SubtitleSeedText;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SpeakerId = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float LengthScale = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float NoiseScale = 0.667f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float NoiseW = 0.8f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float SentenceSilenceSeconds = 0.2f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferCuda = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString AudioEncodingHint = TEXT("wav-pcm16-mono");
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !RequestId.IsEmpty()
|
||||
&& !NarrationContractId.IsEmpty()
|
||||
&& !ServiceLaneId.IsEmpty()
|
||||
&& !OutputRouteId.IsEmpty()
|
||||
&& !VoiceProfileId.IsEmpty()
|
||||
&& !LanguageCode.IsEmpty()
|
||||
&& !ScriptText.IsEmpty()
|
||||
&& !AudioEncodingHint.IsEmpty()
|
||||
&& LengthScale > 0.0f
|
||||
&& NoiseScale >= 0.0f
|
||||
&& NoiseW >= 0.0f
|
||||
&& SentenceSilenceSeconds >= 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistNarrationSynthesisResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RequestId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NarrationContractId = TEXT("embodied-companion-narration");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ServiceLaneId = TEXT("voice/local-sidecar");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString OutputRouteId = TEXT("companion/narration/local");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VoiceProfileId = TEXT("en_US-lessac-medium");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LanguageCode = TEXT("en_US");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SubtitleText;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString AudioEncoding = TEXT("wav-pcm16-mono");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SampleRateHz = 16000;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ChannelCount = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AppliedLengthScale = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AppliedNoiseScale = 0.667f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AppliedNoiseW = 0.8f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AppliedSentenceSilenceSeconds = 0.2f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<uint8> AudioBytes;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Warnings;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !RequestId.IsEmpty()
|
||||
&& !NarrationContractId.IsEmpty()
|
||||
&& !ServiceLaneId.IsEmpty()
|
||||
&& !OutputRouteId.IsEmpty()
|
||||
&& !VoiceProfileId.IsEmpty()
|
||||
&& !LanguageCode.IsEmpty()
|
||||
&& !SubtitleText.IsEmpty()
|
||||
&& !AudioEncoding.IsEmpty()
|
||||
&& SampleRateHz > 0
|
||||
&& ChannelCount > 0
|
||||
&& DurationMs >= 0
|
||||
&& AudioBytes.Num() > 0
|
||||
&& AppliedLengthScale > 0.0f
|
||||
&& AppliedNoiseScale >= 0.0f
|
||||
&& AppliedNoiseW >= 0.0f
|
||||
&& AppliedSentenceSilenceSeconds >= 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistVoiceServiceHealth
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ProviderLabel = TEXT("mock");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ServiceVersion = TEXT("mock-voice/v1");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ServiceEndpoint;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Capabilities;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> SupportedVoiceProfileIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastError;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bReady = true;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !ProviderLabel.IsEmpty() && !ServiceVersion.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionTypes.h"
|
||||
#include "HyperTwistVoiceClient.generated.h"
|
||||
|
||||
UINTERFACE(BlueprintType)
|
||||
class UNREALHYPERTWIST_API UHyperTwistVoiceClient : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class UNREALHYPERTWIST_API IHyperTwistVoiceClient
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual TArray<FHyperTwistVoiceProfileSummary> ListVoiceProfiles() const = 0;
|
||||
virtual FHyperTwistNarrationSynthesisResult SynthesizeNarration(
|
||||
const FHyperTwistNarrationSynthesisRequest& Request
|
||||
) = 0;
|
||||
virtual FHyperTwistVoiceServiceHealth GetVoiceServiceHealth() const = 0;
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class UNREALHYPERTWIST_API UHyperTwistMockVoiceClient : public UObject, public IHyperTwistVoiceClient
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Recognition")
|
||||
virtual TArray<FHyperTwistVoiceProfileSummary> ListVoiceProfiles() const override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Recognition")
|
||||
virtual FHyperTwistNarrationSynthesisResult SynthesizeNarration(
|
||||
const FHyperTwistNarrationSynthesisRequest& Request
|
||||
) override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Recognition")
|
||||
virtual FHyperTwistVoiceServiceHealth GetVoiceServiceHealth() const override;
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType, Config = Game, DefaultConfig)
|
||||
class UNREALHYPERTWIST_API UHyperTwistHttpVoiceClient : public UObject, public IHyperTwistVoiceClient
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
FString ProviderLabel = TEXT("local-http-sidecar");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
FString ServiceBaseUrl = TEXT("http://127.0.0.1:8766");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
FString ProfilesPath = TEXT("/voice/profiles");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
FString SynthesizePath = TEXT("/voice/narrate");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
FString HealthPath = TEXT("/voice/health");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
float RequestTimeoutSeconds = 5.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "HyperTwist|Recognition")
|
||||
FString AuthorizationToken;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Recognition")
|
||||
virtual TArray<FHyperTwistVoiceProfileSummary> ListVoiceProfiles() const override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Recognition")
|
||||
virtual FHyperTwistNarrationSynthesisResult SynthesizeNarration(
|
||||
const FHyperTwistNarrationSynthesisRequest& Request
|
||||
) override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Recognition")
|
||||
virtual FHyperTwistVoiceServiceHealth GetVoiceServiceHealth() const override;
|
||||
|
||||
private:
|
||||
mutable FString LastTransportError;
|
||||
};
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
// 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 HyperTwistPiperPhase6RGTestInternal
|
||||
{
|
||||
UHyperTwistMockVoiceClient* MakeMockVoiceClient()
|
||||
{
|
||||
return NewObject<UHyperTwistMockVoiceClient>(GetTransientPackage());
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistPiperPhase6RGNarrationRequestTest,
|
||||
"HyperTwist.Permissive.Piper.Phase6R.G.NarrationRequest",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistPiperPhase6RGNarrationRequestTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingCompanionNarrationContract NarrationContract;
|
||||
TestTrue(
|
||||
TEXT("The bundled embodied companion narration contract must be available."),
|
||||
UHyperTwistTrainingRuntimeLibrary::TryGetBundledEmbodiedCompanionNarrationContract(
|
||||
TEXT("embodied-companion-narration"),
|
||||
NarrationContract
|
||||
)
|
||||
);
|
||||
TestTrue(TEXT("The bundled narration contract must be structurally valid."), NarrationContract.IsStructurallyValid());
|
||||
TestTrue(
|
||||
TEXT("The narration contract must preserve subtitle synchronization in the output surface tags."),
|
||||
NarrationContract.OutputSurfaceTags.Contains(TEXT("subtitle-sync"))
|
||||
);
|
||||
|
||||
UHyperTwistMockVoiceClient* VoiceClient = HyperTwistPiperPhase6RGTestInternal::MakeMockVoiceClient();
|
||||
TestNotNull(TEXT("The mock voice client must be constructed for Phase 6R-G."), VoiceClient);
|
||||
if (VoiceClient == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const TArray<FHyperTwistVoiceProfileSummary> Profiles = VoiceClient->ListVoiceProfiles();
|
||||
TestTrue(TEXT("The bounded piper route must expose a non-empty local voice catalog."), Profiles.Num() >= 3);
|
||||
TestTrue(
|
||||
TEXT("The local voice catalog must contain the bounded Lessac profile."),
|
||||
Profiles.ContainsByPredicate([](const FHyperTwistVoiceProfileSummary& Profile)
|
||||
{
|
||||
return Profile.VoiceProfileId == TEXT("en_US-lessac-medium");
|
||||
})
|
||||
);
|
||||
|
||||
const FHyperTwistNarrationSynthesisRequest Request = UHyperTwistContractLibrary::MakeSampleNarrationSynthesisRequest();
|
||||
TestTrue(TEXT("The sample narration request must be structurally valid."), Request.IsStructurallyValid());
|
||||
TestEqual(
|
||||
TEXT("The request must anchor on the bundled embodied companion narration contract."),
|
||||
Request.NarrationContractId,
|
||||
TEXT("embodied-companion-narration")
|
||||
);
|
||||
TestEqual(TEXT("The bounded first slice must stay on the local voice sidecar lane."), Request.ServiceLaneId, TEXT("voice/local-sidecar"));
|
||||
TestEqual(TEXT("The bounded first slice must keep the local companion narration route."), Request.OutputRouteId, TEXT("companion/narration/local"));
|
||||
TestEqual(TEXT("The bounded first slice must use WAV mono output."), Request.AudioEncodingHint, TEXT("wav-pcm16-mono"));
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistPiperPhase6RGSynthesisResultTest,
|
||||
"HyperTwist.Permissive.Piper.Phase6R.G.SynthesisResult",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistPiperPhase6RGSynthesisResultTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UHyperTwistMockVoiceClient* VoiceClient = HyperTwistPiperPhase6RGTestInternal::MakeMockVoiceClient();
|
||||
TestNotNull(TEXT("The mock voice client must be constructed for Phase 6R-G."), VoiceClient);
|
||||
if (VoiceClient == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistNarrationSynthesisRequest Request = UHyperTwistContractLibrary::MakeSampleNarrationSynthesisRequest();
|
||||
const FHyperTwistNarrationSynthesisResult Result = VoiceClient->SynthesizeNarration(Request);
|
||||
|
||||
TestTrue(TEXT("The bounded narration synthesis result must be structurally valid."), Result.IsStructurallyValid());
|
||||
TestEqual(TEXT("The result must preserve the request id."), Result.RequestId, Request.RequestId);
|
||||
TestEqual(TEXT("The result must preserve the voice profile id."), Result.VoiceProfileId, Request.VoiceProfileId);
|
||||
TestEqual(
|
||||
TEXT("The result must preserve the subtitle seed text for the local narration sidecar."),
|
||||
Result.SubtitleText,
|
||||
Request.SubtitleSeedText
|
||||
);
|
||||
TestEqual(TEXT("The result must stay WAV mono."), Result.AudioEncoding, TEXT("wav-pcm16-mono"));
|
||||
TestEqual(TEXT("The bounded local sidecar must keep the donor-grounded sample rate."), Result.SampleRateHz, 16000);
|
||||
TestEqual(TEXT("The bounded local sidecar must keep mono output."), Result.ChannelCount, 1);
|
||||
TestTrue(TEXT("The bounded local sidecar must return synthesized audio bytes."), Result.AudioBytes.Num() > 44);
|
||||
TestTrue(TEXT("The bounded local sidecar must report positive duration."), Result.DurationMs > 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistPiperPhase6RGServiceHealthTest,
|
||||
"HyperTwist.Permissive.Piper.Phase6R.G.ServiceHealth",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistPiperPhase6RGServiceHealthTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UHyperTwistMockVoiceClient* VoiceClient = HyperTwistPiperPhase6RGTestInternal::MakeMockVoiceClient();
|
||||
TestNotNull(TEXT("The mock voice client must be constructed for Phase 6R-G."), VoiceClient);
|
||||
if (VoiceClient == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistVoiceServiceHealth Health = VoiceClient->GetVoiceServiceHealth();
|
||||
TestTrue(TEXT("The bounded voice service health must be structurally valid."), Health.IsStructurallyValid());
|
||||
TestTrue(TEXT("The bounded voice service health must report ready."), Health.bReady);
|
||||
TestTrue(TEXT("The bounded voice service must expose voice catalog capability."), Health.Capabilities.Contains(TEXT("voiceCatalog")));
|
||||
TestTrue(TEXT("The bounded voice service must expose offline narration capability."), Health.Capabilities.Contains(TEXT("offlineNarration")));
|
||||
TestTrue(TEXT("The bounded voice service must expose WAV output capability."), Health.Capabilities.Contains(TEXT("wavOutput")));
|
||||
TestTrue(TEXT("The bounded voice service must expose subtitle-seed capability."), Health.Capabilities.Contains(TEXT("subtitleSeed")));
|
||||
TestTrue(
|
||||
TEXT("The bounded voice service must advertise the retained Lessac voice profile."),
|
||||
Health.SupportedVoiceProfileIds.Contains(TEXT("en_US-lessac-medium"))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -136,8 +136,12 @@ Status update on `2026-05-21`:
|
|||
- the bounded permissive `Phase 6R-F` `SYSTRAN/faster-whisper` transcription-service
|
||||
orchestration packet is now landed in current code
|
||||
- `SYSTRAN/faster-whisper` remains partially incorporated
|
||||
- the current next bounded move is a source-backed `Phase 6R-G` `rhasspy/piper`
|
||||
preparation/control pass for a bounded voice-output sidecar seam, not a new restrictive packet
|
||||
- the generic source-backed `Phase 6R-G` `rhasspy/piper` control pass is now consumed
|
||||
- the bounded permissive `Phase 6R-G` `rhasspy/piper` local narration sidecar packet is now
|
||||
landed in current code
|
||||
- `rhasspy/piper` remains partially incorporated
|
||||
- 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, not a new restrictive packet
|
||||
by default
|
||||
- use the repo-row README census, the portfolio standing refresh backfill, and the `2R-A`
|
||||
ownership contract for the current queue after that correction
|
||||
|
|
@ -145,12 +149,12 @@ Status update on `2026-05-21`:
|
|||
This file now also preserves the current truth that future models must not lose:
|
||||
|
||||
- current curated HyperTwist shallow-eval set: `75` repos
|
||||
- currently verified live/implemented in checked `UnrealHyperTwist` surfaces: `30`
|
||||
- permissive live lanes: `19`
|
||||
- currently verified live/implemented in checked `UnrealHyperTwist` surfaces: `31`
|
||||
- permissive live lanes: `20`
|
||||
- boundary-sensitive live lanes: `6`
|
||||
- restrictive live lanes: `5`
|
||||
|
||||
The nineteen permissive live lanes are:
|
||||
The twenty permissive live lanes are:
|
||||
|
||||
- `Aarav2709/KubeTimr`
|
||||
- `HactarCE/Hyperspeedcube`
|
||||
|
|
@ -171,6 +175,7 @@ The nineteen permissive live lanes are:
|
|||
- `roice3/MagicTile`
|
||||
- `ggml-org/whisper.cpp`
|
||||
- `SYSTRAN/faster-whisper`
|
||||
- `rhasspy/piper`
|
||||
|
||||
The six boundary-sensitive live lanes are:
|
||||
|
||||
|
|
@ -1345,6 +1350,7 @@ Decision date:
|
|||
|
||||
- `2026-04-24`
|
||||
- refreshed on `2026-05-13`
|
||||
- refreshed on `2026-05-22`
|
||||
|
||||
Current licensing judgment:
|
||||
|
||||
|
|
@ -1369,8 +1375,12 @@ Approved working posture:
|
|||
`MIT` code posture
|
||||
- do not over-elevate it into the broader voice strategy; its main strength is lightweight offline
|
||||
narration
|
||||
- the current next bounded move is a source-backed `Phase 6R-G` preparation/control pass for one
|
||||
bounded voice-output sidecar seam
|
||||
- the first bounded local narration sidecar slice is now landed:
|
||||
- local voice-profile catalog normalization
|
||||
- narration synthesis request/result contract
|
||||
- mock and HTTP local sidecar voice-client seam
|
||||
- local voice service-health exposure
|
||||
- `rhasspy/piper` remains a bounded donor, not the broad voice-platform owner
|
||||
- keep voice-asset review separate from the code-license judgment
|
||||
|
||||
### `screenpipe/screenpipe`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
# HyperTwist Phase 6R-G piper local narration sidecar implementation packet
|
||||
|
||||
Created on `2026-05-22`
|
||||
|
||||
## Status
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded permissive `Phase 6R-G` implementation slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet lands the first narrower bounded slice from the retained `rhasspy/piper` voice-output row.
|
||||
|
||||
The landed slice is:
|
||||
|
||||
- first-party local narration sidecar contract above the existing embodied companion narration lane
|
||||
|
||||
It is not:
|
||||
|
||||
- a broad TTS platform packet
|
||||
- a downloadable voice-asset packet
|
||||
- a provider-profile or BYOK packet
|
||||
- a viseme / gesture runtime packet
|
||||
- a `coqui-ai/TTS` widening 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_G_PIPER_LOCAL_NARRATION_SIDECAR_PREPARATION_PACKET_2026-05-22.md`
|
||||
|
||||
The retained owner remains:
|
||||
|
||||
- `rhasspy/piper`
|
||||
|
||||
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 `rhasspy/piper` bounded to the narrower `A1 / R1 / F2` local narration sidecar slice
|
||||
|
||||
## Landed scope
|
||||
|
||||
The current code now owns a retained local narration sidecar contract through:
|
||||
|
||||
- retained recognition contract types for:
|
||||
- `FHyperTwistVoiceProfileSummary`
|
||||
- `FHyperTwistVoiceProfileCatalog`
|
||||
- `FHyperTwistNarrationSynthesisRequest`
|
||||
- `FHyperTwistNarrationSynthesisResult`
|
||||
- `FHyperTwistVoiceServiceHealth`
|
||||
- sample voice-profile, narration-request, narration-result, and service-health outputs in:
|
||||
- `UHyperTwistContractLibrary`
|
||||
- direct-donor voice client seam in:
|
||||
- `UHyperTwistMockVoiceClient`
|
||||
- `UHyperTwistHttpVoiceClient`
|
||||
- first-party config defaults for the bounded local voice sidecar lane in:
|
||||
- `DefaultGame.ini`
|
||||
- focused automation coverage in:
|
||||
- `HyperTwistPiperPhase6RGLocalNarrationContractTest.cpp`
|
||||
|
||||
## Why this is still intentionally bounded
|
||||
|
||||
This packet lands the first voice-output donor seam, but it does not widen into the neighboring retained families.
|
||||
|
||||
Still deferred:
|
||||
|
||||
- downloadable voice/model review
|
||||
- bundled voice asset shipping
|
||||
- rich playback runtime ownership
|
||||
- viseme / gesture runtime integration
|
||||
- provider-profile and BYOK custody
|
||||
- broad assistant-platform scope
|
||||
- `coqui-ai/TTS`
|
||||
|
||||
## Validation
|
||||
|
||||
Build validation:
|
||||
|
||||
- `Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE`
|
||||
|
||||
Focused automation validation:
|
||||
|
||||
- `Automation RunTests HyperTwist.Permissive.Piper.Phase6R.G`
|
||||
|
||||
Regression automation validation:
|
||||
|
||||
- `Automation RunTests HyperTwist.Permissive.FasterWhisper.Phase6R.F`
|
||||
- `Automation RunTests HyperTwist.Permissive.WhisperCpp.Phase6R.E`
|
||||
- `Automation RunTests HyperTwist.CleanRoom.CubeDesk`
|
||||
|
||||
Expected covered tests:
|
||||
|
||||
- `NarrationRequest`
|
||||
- `SynthesisResult`
|
||||
- `ServiceHealth`
|
||||
- `TranscriptMetadata`
|
||||
- `SessionConfig`
|
||||
- `TranscriptResult`
|
||||
- existing `CubeDesk` clean-room regression suite
|
||||
|
||||
## Queue effect
|
||||
|
||||
This packet consumes the current `Phase 6R-G` implementation slice.
|
||||
|
||||
`rhasspy/piper` remains only partially incorporated:
|
||||
|
||||
- landed now:
|
||||
- local voice-profile catalog normalization
|
||||
- narration synthesis request/result contract
|
||||
- mock and HTTP local sidecar voice-client seam
|
||||
- local voice service-health exposure
|
||||
- still deferred:
|
||||
- downloadable voice/model review
|
||||
- bundled voice asset shipping
|
||||
- rich playback runtime ownership
|
||||
- viseme / gesture runtime integration
|
||||
- broad assistant-platform scope
|
||||
|
||||
The next clean move is:
|
||||
|
||||
- source-backed `Phase 6R-H` `coqui-ai/TTS` 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
|
||||
- `rhasspy/piper`
|
||||
- keep it bounded to the local narration sidecar seam rather than broad voice-platform ownership
|
||||
- `coqui-ai/TTS`
|
||||
- keep future downloadable voice/model review separate from the code-license judgment
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# HyperTwist Phase 6R-G piper local narration sidecar preparation packet
|
||||
|
||||
Created on `2026-05-22`
|
||||
|
||||
## Status
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- source-backed permissive `Phase 6R-G` preparation/control slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet narrows the retained `rhasspy/piper` row before implementation.
|
||||
|
||||
The retained first slice is:
|
||||
|
||||
- local narration sidecar contract above the already landed first-party companion narration boundary
|
||||
|
||||
It is not:
|
||||
|
||||
- a broad voice-platform packet
|
||||
- a downloadable voice-asset or model-shipping packet
|
||||
- a provider-profile or BYOK packet
|
||||
- a `coqui-ai/TTS` widening packet
|
||||
- a full companion playback, viseme, or avatar-orchestration packet
|
||||
|
||||
## Source-backed basis
|
||||
|
||||
Primary donor surfaces inspected:
|
||||
|
||||
- `README.md`
|
||||
- `VOICES.md`
|
||||
- `src/python_run/README_http.md`
|
||||
- `src/python_run/piper/http_server.py`
|
||||
- `src/python_run/piper/voice.py`
|
||||
- `src/python_run/piper/voices.json`
|
||||
- `etc/test_voice.onnx.json`
|
||||
|
||||
## Narrowing decision
|
||||
|
||||
The smallest strong donor seam is:
|
||||
|
||||
- voice-profile catalog metadata
|
||||
- narration synthesis request/result normalization
|
||||
- local HTTP-sidecar narration transport shape returning WAV output
|
||||
|
||||
The first bounded implementation packet should therefore land only:
|
||||
|
||||
- first-party voice-profile summaries
|
||||
- first-party narration synthesis request/result contracts
|
||||
- first-party mock and HTTP voice-client seam
|
||||
- focused automation around the bounded local narration route
|
||||
|
||||
## Explicit deferrals
|
||||
|
||||
Still deferred after this control pass:
|
||||
|
||||
- downloadable voice/model review
|
||||
- bundled voice asset shipping
|
||||
- rich playback runtime ownership
|
||||
- viseme or gesture runtime integration
|
||||
- broader voice-policy ownership
|
||||
- `coqui-ai/TTS`
|
||||
|
||||
## Queue effect
|
||||
|
||||
This preparation packet authorizes one bounded permissive implementation packet:
|
||||
|
||||
- `Phase 6R-G` local narration sidecar contract
|
||||
|
||||
After that packet, move the queue head to:
|
||||
|
||||
- source-backed `Phase 6R-H` `coqui-ai/TTS` preparation/control pass
|
||||
|
||||
Keep the legal guard visible:
|
||||
|
||||
- code-license judgment stays separate from voice/model/payload review
|
||||
|
|
@ -80,9 +80,9 @@ Use these as the current governing docs:
|
|||
|
||||
The next bounded move is now:
|
||||
|
||||
1. source-backed `Phase 6R-G` `rhasspy/piper` preparation/control pass
|
||||
2. keep it behind the landed `whisper.cpp` and `faster-whisper` STT slices
|
||||
3. narrow the first widening slice to one bounded voice-output sidecar seam
|
||||
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
|
||||
|
||||
## Memory-specific sequencing rule
|
||||
|
|
|
|||
|
|
@ -182,7 +182,8 @@ repo.
|
|||
| Speech transcript session boundary | Implemented now | landed `whisper.cpp` packet | Current bounded STT truth. |
|
||||
| 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. |
|
||||
| Voice output / narration sidecars | Deep-source grounded retained | `piper`, `coqui-ai/TTS` retained rows | Retained for later bounded work. |
|
||||
| Local narration sidecar seam | Implemented now | landed `piper` packet | Current bounded offline narration contract, voice catalog, and local sidecar truth. |
|
||||
| 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. |
|
||||
|
||||
### 8. Provider-neutral AI/provider routing
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Canonical discovery surfaces for roadmap interpretation:
|
|||
for skill-layer sequencing
|
||||
|
||||
- the canonical HyperTwist repo-row portfolio is now treated as `75` rows, not `71`
|
||||
- currently implemented rows are now `29`, not `20`
|
||||
- currently implemented rows are now `30`, not `20`
|
||||
- four of the additional current implemented rows are the later-landed restrictive clean-room lanes:
|
||||
- `cubing/alg.js`
|
||||
- `cubing/twisty.js`
|
||||
|
|
@ -31,6 +31,8 @@ Canonical discovery surfaces for roadmap interpretation:
|
|||
- `roice3/MagicTile`
|
||||
- the ninth additional current implemented row is the same-day bounded permissive partial row:
|
||||
- `SYSTRAN/faster-whisper`
|
||||
- the tenth additional current implemented row is the next-day bounded permissive partial row:
|
||||
- `rhasspy/piper`
|
||||
- the current next bounded move is **not** another restrictive packet by default
|
||||
- the earlier first-party packet block that was still being treated as next is now confirmed landed:
|
||||
- `d4f4ad3` `Add primary coach orchestration entry lane`
|
||||
|
|
@ -71,8 +73,13 @@ Canonical discovery surfaces for roadmap interpretation:
|
|||
orchestration packet is now landed in current code
|
||||
- `SYSTRAN/faster-whisper` remains partially incorporated; microphone capture, BYOK/profile
|
||||
custody, payload shipping, and voice-output ownership stay deferred
|
||||
- the current next bounded move is a source-backed `Phase 6R-G` `rhasspy/piper`
|
||||
preparation/control pass for a bounded voice-output sidecar seam
|
||||
- the generic source-backed `Phase 6R-G` `rhasspy/piper` control pass is now consumed
|
||||
- the bounded permissive `Phase 6R-G` `rhasspy/piper` local narration sidecar packet is now landed
|
||||
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 repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than
|
||||
waiting on another first-party packet
|
||||
|
||||
|
|
@ -100,8 +107,8 @@ Donor-strength rule:
|
|||
- route difficulty changes how retained value may enter the product, not whether it may win technically
|
||||
|
||||
- current shallow-eval set: `75` repos
|
||||
- currently verified live/implemented in checked Unreal surfaces: `30`
|
||||
- permissive live lanes: `19`
|
||||
- currently verified live/implemented in checked Unreal surfaces: `31`
|
||||
- permissive live lanes: `20`
|
||||
- boundary-sensitive live lanes: `6`
|
||||
- restrictive live lanes: `5`
|
||||
- the restrictive landed lanes are:
|
||||
|
|
@ -134,8 +141,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-G`
|
||||
`rhasspy/piper` preparation/control pass.
|
||||
The next bounded move is a source-backed `Phase 6R-H`
|
||||
`coqui-ai/TTS` preparation/control pass.
|
||||
|
||||
Queue interpretation after that packet:
|
||||
|
||||
|
|
@ -204,8 +211,21 @@ Queue interpretation after that packet:
|
|||
- downloadable model or payload shipping
|
||||
- TTS or voice-output ownership
|
||||
- broad assistant-platform scope
|
||||
- `rhasspy/piper` is now landed as a partially incorporated row rather than a generic future
|
||||
placeholder:
|
||||
- landed:
|
||||
- local narration sidecar contract
|
||||
- voice-profile catalog normalization
|
||||
- narration synthesis request/result contract
|
||||
- bounded mock and HTTP voice-client seam
|
||||
- voice service-health exposure
|
||||
- still deferred:
|
||||
- downloadable voice/model review
|
||||
- bundled voice asset shipping
|
||||
- rich playback runtime ownership
|
||||
- viseme / gesture runtime integration
|
||||
- broad assistant-platform scope
|
||||
- the next queue shape is now:
|
||||
- `rhasspy/piper`
|
||||
- `coqui-ai/TTS`
|
||||
- keep the speech-input / voice sidecar legal sequencing guard visible:
|
||||
- keep code-license judgments separate from model, voice, and payload-license review
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue