Jump to content

Rennas Ranch Voice Files


Recommended Posts

Posted

Rennas Ranch Voice Files

View File

Voice Files for Rennas Ranch created with XVASynth 3.0


These Files are not optimized, so they contain Voices which are not used in the Mod, thats why the files are so big, but I really haven't had it in me to find out which lines are spoken by whom.

Due to the upload restrictions of 500MB I had to split it.

 

If  there are any problems, or voices missing, please let me know!

 

PS: I created this Files for MY PLAYTHROUGH! But sharing is caring, as always. 😉

Due to the way I am exporting the lines with a script through SSEdit and the way the mod is configured the size of this Voice mod is at least 6-12 times as big as it has to be. I allready contacted the Mod Author, but I think he has enough to do to add more content to the mod, then to fix the voice lines, and I am totally ok with that! So please be patient, I think he'll has a lot on his plate right now.

 

I just started my playthrough, so I dont know if anything is missing, for now it seems to work.
 

 


  • Submitter
  • Submitted
    12/16/25
  • Category
  • Requirements
    Rennas Ranch
  • Regular Edition Compatible
    Not Applicable
  • Install Instructions

    Install as any other Mod

 

Posted

How are you people so fast with these voice files? Do you manually have to create them string by string? Or does the program just do it for  you? 

 

I could maybe automate this process for you guys if it's tedious. 

Posted
9 hours ago, asdt123123 said:

How are you people so fast with these voice files? Do you manually have to create them string by string? Or does the program just do it for  you? 

 

I could maybe automate this process for you guys if it's tedious. 

 

Actually I could use your help, If you want have a look here, there I have described a problem, with the way I export the lines with SSEdit.

 

Posted (edited)
15 hours ago, bigbasi said:

 

Actually I could use your help, If you want have a look here, there I have described a problem, with the way I export the lines with SSEdit.

 

I wrote a quick script that does essentially what you're asking:

 

Spoiler
{
  Export dialogues for Skyrim SE for use with xVASynth.
  VERSION: 2.7
}
unit SkyrimSEExportDialogues;

var
  slExport, slVoiceTypes, slIDs: TStringList;
  bDebug: Boolean;


procedure DebugLog(s: string);
begin
  if bDebug then AddMessage('[DEBUG] ' + s);
end;


function InfoFileName(PluginName: string; InfoFormID: integer; RespNumber: string): string;
begin
  PluginName := ChangeFileExt(PluginName, '');
  Result := Format('%s_%s_%s', [
    PluginName,
    IntToHex(InfoFormID and $FFFFFF, 8),
    RespNumber
  ]);
end;


//heelper to get Editor ID of a Voice Type link safely
function GetVoiceIDFromLink(e: IInterface): string;
var
  rec: IInterface;
begin
  Result := '';
  if not Assigned(e) then Exit;
  
  rec := WinningOverride(LinksTo(e));
  if Assigned(rec) and (Signature(rec) = 'VTYP') then
    Result := EditorID(rec);
end;


function GetVoiceTypeFromNPC(e: IInterface): string;
var
  vtyp, race, baseNPC, tplt, vList, child: IInterface;
  sig, tpltFlags: string;
  iter, i: integer;
begin
  Result := '';
  if not Assigned(e) then Exit;
  e := WinningOverride(e);
  sig := Signature(e);
  
  DebugLog('      -> Resolving Actor: ' + Name(e));

  //  Resolve Reference (ACHR/REFR) to Base NPC
  if (sig = 'ACHR') or (sig = 'REFR') then begin
    baseNPC := WinningOverride(LinksTo(ElementBySignature(e, 'NAME')));
    if Assigned(baseNPC) then begin
      e := baseNPC;
      sig := Signature(e);
      DebugLog('        -> Resolved Reference to Base: ' + Name(e));
    end else begin
      DebugLog('        -> Could not resolve Base NPC for Reference.');
      Exit; 
    end;
  end;

  if sig = 'NPC_' then begin
    
    // Handle TEMPLATES
    for iter := 0 to 5 do begin
       tpltFlags := GetElementEditValues(e, 'ACBS\Template Flags');
       if Pos('Use Traits', tpltFlags) > 0 then begin
         tplt := WinningOverride(LinksTo(ElementBySignature(e, 'TPLT')));
         if Assigned(tplt) then begin
            DebugLog('        -> Inherits Traits from Template: ' + Name(tplt));
            e := tplt;
            Continue;
         end;
       end;
       Break; 
    end;

    // Try Explicit VTYP 
    vtyp := ElementBySignature(e, 'VTYP');
    if not Assigned(vtyp) then vtyp := ElementByName(e, 'VTYP - Voice Type');
    
    // Try VTCK on NPC
    if not Assigned(vtyp) then vtyp := ElementBySignature(e, 'VTCK');

    if Assigned(vtyp) then begin
      Result := GetVoiceIDFromLink(vtyp);
      if Result <> '' then begin
        DebugLog('        -> Found NPC Voice: ' + Result);
        Exit;
      end;
    end;

    // Fallback: Check Race (RNAM) -> VTCK (Voices To Check)
    // Get WinningOverride of the Race to ensure we see lists added by mods/patches
    race := WinningOverride(LinksTo(ElementBySignature(e, 'RNAM')));
    
    if Assigned(race) then begin
      DebugLog('        -> Checking Race: ' + Name(race));
      
      // Try Standard VTCK Signature
      vList := ElementBySignature(race, 'VTCK');
      
      // If not found, try searching children (Brute force for "Voices")
      if not Assigned(vList) then begin
         for i := 0 to ElementCount(race) - 1 do begin
             child := ElementByIndex(race, i);
             if (Signature(child) = 'VTCK') or (Name(child) = 'Voices') then begin
                vList := child;
                Break;
             end;
         end;
      end;

      if Assigned(vList) and (ElementCount(vList) > 0) then begin
         // Grab the first one in the list
         // ElementByIndex(vList, 0) gives the reference inside the list
         vtyp := ElementByIndex(vList, 0); 
         Result := GetVoiceIDFromLink(vtyp);
         
         if Result <> '' then
            DebugLog('        -> Found Race Default Voice: ' + Result)
         else
            DebugLog('        -> Race Voice List found, but link was nil.');
            
      end else begin
         DebugLog('        -> Race has no Voice List (VTCK) or list is empty.');
      end;
    end else begin
       DebugLog('        -> NPC has no Race link.');
    end;

  end else begin
    DebugLog('        -> Element is not an NPC_');
  end;
end;


function GetVoiceTypeName(link: IInterface): string;
begin
  Result := GetVoiceIDFromLink(link);
end;


procedure GetStandardVoicesFromSex(lst: TStringList; male : Boolean);
begin
  if male then begin
    lst.Add('MaleArgonian'); lst.Add('MaleBandit'); lst.Add('MaleBrute');
    lst.Add('MaleCommoner'); lst.Add('MaleCommonerAccented'); lst.Add('MaleCondescending');
    lst.Add('MaleCoward'); lst.Add('MaleDarkElf'); lst.Add('MaleDrunk');
    lst.Add('MaleElfHaughty'); lst.Add('MaleEvenToned'); lst.Add('MaleEvenTonedAccented');
    lst.Add('MaleGuard'); lst.Add('MaleKhajiit'); lst.Add('MaleNord');
    lst.Add('MaleNordCommander'); lst.Add('MaleOldGrumpy'); lst.Add('MaleOldKindly');
    lst.Add('MaleOrc'); lst.Add('MaleSlyCynical'); lst.Add('MaleSoldier');
    lst.Add('MaleWarlock'); lst.Add('MaleYoungEager');
  end else begin
    lst.Add('FemaleArgonian'); lst.Add('FemaleCommander'); lst.Add('FemaleCommoner');
    lst.Add('FemaleCondescending'); lst.Add('FemaleCoward'); lst.Add('FemaleDarkElf');
    lst.Add('FemaleElfHaughty'); lst.Add('FemaleEvenToned'); lst.Add('FemaleKhajiit');
    lst.Add('FemaleNord'); lst.Add('FemaleOldGrumpy'); lst.Add('FemaleOldKindly');
    lst.Add('FemaleOrc'); lst.Add('FemaleShrill'); lst.Add('FemaleSultry');
    lst.Add('FemaleYoungEager');
  end;
end;


function ParseConditions(conditions: IInterface; lstVoiceTypes, lstIDs: TStringList): string;
var
  condItem, ctda, param1: IInterface;
  i, typ: integer;
  funcStr, sVal: string;
  sl: TStringList;
  compVal: Double;
begin
  sl := TStringList.Create;
  
  if ElementCount(conditions) > 0 then DebugLog('Scanning ' + IntToStr(ElementCount(conditions)) + ' conditions...');

  for i := 0 to ElementCount(conditions) - 1 do 
  begin
    condItem := ElementByIndex(conditions, i);
    ctda := ElementBySignature(condItem, 'CTDA');
    if not Assigned(ctda) then ctda := condItem;

    funcStr := GetElementEditValues(ctda, 'Function');
    typ := GetElementNativeValues(ctda, 'Type');
    compVal := GetElementNativeValues(ctda, 'Comparison Value');
    
    DebugLog('  [Cond ' + IntToStr(i) + '] Func: "' + funcStr + '" | Type: ' + IntToStr(typ) + ' | Val: ' + FloatToStr(compVal));

    // SKIP: Run On Target (Bit 1)
    if (typ and 2) > 0 then begin
       DebugLog('    -> Skipping (Run On Target)');
       Continue;
    end;

    // PARAM 1
    param1 := ElementByName(ctda, 'Parameter #1');
    if not Assigned(param1) then param1 := ElementByIndex(ctda, 5);

    // CASE 1: GetIsID
    if funcStr = 'GetIsID' then 
    begin
      if (Abs(compVal - 1.0) < 0.01) then
      begin
        DebugLog('    -> Found GetIsID. Checking Actor...');
        sVal := GetVoiceTypeFromNPC(LinksTo(param1));
        if sVal <> '' then begin
           sl.Add(sVal);
           DebugLog('    -> SUCCESS: Added Voice: ' + sVal);
        end else begin
           DebugLog('    -> FAIL: Could not extract voice from Actor ID.');
        end;
      end;
    end
      
    // CASE 2: GetIsVoiceType
    else if funcStr = 'GetIsVoiceType' then
    begin
       if (Abs(compVal - 1.0) < 0.01) then
       begin
         sVal := GetVoiceTypeName(param1);
         if sVal <> '' then begin
            sl.Add(sVal);
            DebugLog('    -> SUCCESS: Added VTYP: ' + sVal);
         end;
       end;
    end
      
    // CASE 3: GetIsSex
    else if funcStr = 'GetIsSex' then 
    begin
      if GetEditValue(param1) = 'Male' then 
         GetStandardVoicesFromSex(sl, True)
      else 
         GetStandardVoicesFromSex(sl, False);
    end;
  end;
    
  lstVoiceTypes.AddStrings(sl);
  sl.Free;
end;


function Initialize: integer;
begin
  bDebug := True; 

  slExport := TStringList.Create;
  slExport.Add('game_id,voice_id,model,out_path,text');
  slVoiceTypes := TStringList.Create;
  slVoiceTypes.Sorted := True;
  slVoiceTypes.Duplicates := dupIgnore;
  slIDs := TStringList.Create;
  
  if bDebug then AddMessage('Initializing Export Script 2.7...');
end;


function Process(e: IInterface): Integer;
var
  dial, responses, response, masterFile: IInterface;
  i, j: integer;
  line, fname, voicename, path, model, voiceTypeStr: string;
begin
  if Signature(e) <> 'INFO' then Exit;

  if bDebug then AddMessage('--------------------------------------------------');
  if bDebug then AddMessage('Processing INFO: ' + IntToHex(FormID(e), 8));

  slVoiceTypes.Clear;
  slIDs.Clear;
  
  ParseConditions(ElementByName(e, 'Conditions'), slVoiceTypes, slIDs);
  
  if slVoiceTypes.Count = 0 then begin
    if bDebug then AddMessage('FALLBACK: No valid voices found in conditions. Exporting ALL.');
    GetStandardVoicesFromSex(slVoiceTypes, True);
    GetStandardVoicesFromSex(slVoiceTypes, False);
  end;
  
  dial := LinksTo(ElementByName(e, 'Topic'));
  responses := ElementByName(e, 'Responses');
  masterFile := MasterOrSelf(e);
  
  for i := 0 to Pred(ElementCount(responses)) do begin
    response := ElementByIndex(responses, i);
    
    fname := InfoFileName(
      GetFileName(masterFile),
      FormID(e),
      GetElementEditValues(response, 'TRDT\Response number')
    );

    for j := 0 to Pred(slVoiceTypes.Count) do begin
      voiceTypeStr := slVoiceTypes[j];
      
      if voiceTypeStr <> '' then
        begin
        voicename := 'Sound\Voice\' + GetFileName(masterFile) + '\' + voiceTypeStr + '\' + fname + '.fuz';
        model := 'sk_' + LowerCase(voiceTypeStr);
        path := 'Data\' + voicename;
        
        line :=
          'skyrim,' + 
          voiceTypeStr + ',' + 
          model + ',' +
          path + ',' +
          '"' + StringReplace(GetElementEditValues(response, 'NAM1'), '"', '''', [rfReplaceAll, rfIgnoreCase]) + '"';
      
        slExport.Add(line);
        end;
    end;
  end;
end;


function Finalize: integer;
var
  dlgSave: TSaveDialog;
begin
  if slExport.Count > 1 then begin
    dlgSave := TSaveDialog.Create(nil);
    dlgSave.Options := dlgSave.Options + [ofOverwritePrompt];
    dlgSave.Filter := 'CSV files (*.csv)|*.csv';
    dlgSave.InitialDir := DataPath;
    dlgSave.FileName := 'skyrimDialogueExport.csv';
    if dlgSave.Execute then begin
      AddMessage('Saving ' + dlgSave.FileName);
      slExport.SaveToFile(dlgSave.FileName);
    end;
    dlgSave.Free;
  end;
  slExport.Free;
  slVoiceTypes.Free;
  slIDs.Free;
end;

end.

 

 

 

Couple of things to note, since I'm not trying to spend a bunch of time on this script:

  • It doesn't check faction conditions. This shouldn't be hard, but I didn't bother. 
  • It DOES check NPC sex conditions, but if it's essentially Player.IsMale, it will add ALL male voice types. You can add a manual check for that condition though
  • I hardcoded voice types I found off the wiki. No clue how you personally handle unique voice types like Cicero so I assumed this is fine
  • Guards add all voice types. I think you can limit this though? I've only heard a handful of voices from guards, but wasn't 100% sure so. 

 

In other words, it's not perfect but I've included tons of debug printing and what not to make it super easy to improve the script. 

Edit: Btw, I plan on releasing a new slavery mod soon. Could you share the quick steps of using that voice generator on these files to save me some time? 

 

I plan to just do exactly what you're doing for my mod. Export voices, generate. I'm assuming this is done in one big batch..? 

Edited by asdt123123
Posted

Hello, I really like the SubmissiveLolaResubmission mod. Some authors have updated Lola's Voice Files before, but they have stopped now. Hex Bolt recently updated SubmissiveLolaResubmission significantly. Could you create its Voice Files?

Posted (edited)
19 hours ago, asdt123123 said:

I wrote a quick script that does essentially what you're asking:

Holy cow... WTF? I read over it and looks ok, I'll give it a try an let you know how it works! Thanks a lot man! This looks so much simpler, than the script I am using now

 

19 hours ago, asdt123123 said:

Btw, I plan on releasing a new slavery mod soon. Could you share the quick steps of using that voice generator on these files to save me some time? 

 

I plan to just do exactly what you're doing for my mod. Export voices, generate. I'm assuming this is done in one big batch..? 


I'm looking forward to your mod, really! I like to play those kind of mod. Once If tested your script I can try to write down the steps I need to do, to mak it work


 

7 hours ago, kingsglaive said:

Hello, I really like the SubmissiveLolaResubmission mod. Some authors have updated Lola's Voice Files before, but they have stopped now. Hex Bolt recently updated SubmissiveLolaResubmission significantly. Could you create its Voice Files?

 

Holy Shit, you are asking for not a small favor Buddy.

Total new lines: 193394, to generate the voice files it'll take weeks. (for the whole mod) No wonder nobody continues to make new voicefiles 😉 But I'll check if I can make a delta voice pack starting from 2.1.8, like I did for Laura, probably faster. 

 

Edit: delta are 24120 new lines, thats doable, should be done tomorrow CET.

Edited by bigbasi
Posted
On 12/17/2025 at 3:02 PM, asdt123123 said:

I wrote a quick script that does essentially what you're asking:

 

  Reveal hidden contents
{
  Export dialogues for Skyrim SE for use with xVASynth.
  VERSION: 2.7
}
unit SkyrimSEExportDialogues;

var
  slExport, slVoiceTypes, slIDs: TStringList;
  bDebug: Boolean;


procedure DebugLog(s: string);
begin
  if bDebug then AddMessage('[DEBUG] ' + s);
end;


function InfoFileName(PluginName: string; InfoFormID: integer; RespNumber: string): string;
begin
  PluginName := ChangeFileExt(PluginName, '');
  Result := Format('%s_%s_%s', [
    PluginName,
    IntToHex(InfoFormID and $FFFFFF, 8),
    RespNumber
  ]);
end;


//heelper to get Editor ID of a Voice Type link safely
function GetVoiceIDFromLink(e: IInterface): string;
var
  rec: IInterface;
begin
  Result := '';
  if not Assigned(e) then Exit;
  
  rec := WinningOverride(LinksTo(e));
  if Assigned(rec) and (Signature(rec) = 'VTYP') then
    Result := EditorID(rec);
end;


function GetVoiceTypeFromNPC(e: IInterface): string;
var
  vtyp, race, baseNPC, tplt, vList, child: IInterface;
  sig, tpltFlags: string;
  iter, i: integer;
begin
  Result := '';
  if not Assigned(e) then Exit;
  e := WinningOverride(e);
  sig := Signature(e);
  
  DebugLog('      -> Resolving Actor: ' + Name(e));

  //  Resolve Reference (ACHR/REFR) to Base NPC
  if (sig = 'ACHR') or (sig = 'REFR') then begin
    baseNPC := WinningOverride(LinksTo(ElementBySignature(e, 'NAME')));
    if Assigned(baseNPC) then begin
      e := baseNPC;
      sig := Signature(e);
      DebugLog('        -> Resolved Reference to Base: ' + Name(e));
    end else begin
      DebugLog('        -> Could not resolve Base NPC for Reference.');
      Exit; 
    end;
  end;

  if sig = 'NPC_' then begin
    
    // Handle TEMPLATES
    for iter := 0 to 5 do begin
       tpltFlags := GetElementEditValues(e, 'ACBS\Template Flags');
       if Pos('Use Traits', tpltFlags) > 0 then begin
         tplt := WinningOverride(LinksTo(ElementBySignature(e, 'TPLT')));
         if Assigned(tplt) then begin
            DebugLog('        -> Inherits Traits from Template: ' + Name(tplt));
            e := tplt;
            Continue;
         end;
       end;
       Break; 
    end;

    // Try Explicit VTYP 
    vtyp := ElementBySignature(e, 'VTYP');
    if not Assigned(vtyp) then vtyp := ElementByName(e, 'VTYP - Voice Type');
    
    // Try VTCK on NPC
    if not Assigned(vtyp) then vtyp := ElementBySignature(e, 'VTCK');

    if Assigned(vtyp) then begin
      Result := GetVoiceIDFromLink(vtyp);
      if Result <> '' then begin
        DebugLog('        -> Found NPC Voice: ' + Result);
        Exit;
      end;
    end;

    // Fallback: Check Race (RNAM) -> VTCK (Voices To Check)
    // Get WinningOverride of the Race to ensure we see lists added by mods/patches
    race := WinningOverride(LinksTo(ElementBySignature(e, 'RNAM')));
    
    if Assigned(race) then begin
      DebugLog('        -> Checking Race: ' + Name(race));
      
      // Try Standard VTCK Signature
      vList := ElementBySignature(race, 'VTCK');
      
      // If not found, try searching children (Brute force for "Voices")
      if not Assigned(vList) then begin
         for i := 0 to ElementCount(race) - 1 do begin
             child := ElementByIndex(race, i);
             if (Signature(child) = 'VTCK') or (Name(child) = 'Voices') then begin
                vList := child;
                Break;
             end;
         end;
      end;

      if Assigned(vList) and (ElementCount(vList) > 0) then begin
         // Grab the first one in the list
         // ElementByIndex(vList, 0) gives the reference inside the list
         vtyp := ElementByIndex(vList, 0); 
         Result := GetVoiceIDFromLink(vtyp);
         
         if Result <> '' then
            DebugLog('        -> Found Race Default Voice: ' + Result)
         else
            DebugLog('        -> Race Voice List found, but link was nil.');
            
      end else begin
         DebugLog('        -> Race has no Voice List (VTCK) or list is empty.');
      end;
    end else begin
       DebugLog('        -> NPC has no Race link.');
    end;

  end else begin
    DebugLog('        -> Element is not an NPC_');
  end;
end;


function GetVoiceTypeName(link: IInterface): string;
begin
  Result := GetVoiceIDFromLink(link);
end;


procedure GetStandardVoicesFromSex(lst: TStringList; male : Boolean);
begin
  if male then begin
    lst.Add('MaleArgonian'); lst.Add('MaleBandit'); lst.Add('MaleBrute');
    lst.Add('MaleCommoner'); lst.Add('MaleCommonerAccented'); lst.Add('MaleCondescending');
    lst.Add('MaleCoward'); lst.Add('MaleDarkElf'); lst.Add('MaleDrunk');
    lst.Add('MaleElfHaughty'); lst.Add('MaleEvenToned'); lst.Add('MaleEvenTonedAccented');
    lst.Add('MaleGuard'); lst.Add('MaleKhajiit'); lst.Add('MaleNord');
    lst.Add('MaleNordCommander'); lst.Add('MaleOldGrumpy'); lst.Add('MaleOldKindly');
    lst.Add('MaleOrc'); lst.Add('MaleSlyCynical'); lst.Add('MaleSoldier');
    lst.Add('MaleWarlock'); lst.Add('MaleYoungEager');
  end else begin
    lst.Add('FemaleArgonian'); lst.Add('FemaleCommander'); lst.Add('FemaleCommoner');
    lst.Add('FemaleCondescending'); lst.Add('FemaleCoward'); lst.Add('FemaleDarkElf');
    lst.Add('FemaleElfHaughty'); lst.Add('FemaleEvenToned'); lst.Add('FemaleKhajiit');
    lst.Add('FemaleNord'); lst.Add('FemaleOldGrumpy'); lst.Add('FemaleOldKindly');
    lst.Add('FemaleOrc'); lst.Add('FemaleShrill'); lst.Add('FemaleSultry');
    lst.Add('FemaleYoungEager');
  end;
end;


function ParseConditions(conditions: IInterface; lstVoiceTypes, lstIDs: TStringList): string;
var
  condItem, ctda, param1: IInterface;
  i, typ: integer;
  funcStr, sVal: string;
  sl: TStringList;
  compVal: Double;
begin
  sl := TStringList.Create;
  
  if ElementCount(conditions) > 0 then DebugLog('Scanning ' + IntToStr(ElementCount(conditions)) + ' conditions...');

  for i := 0 to ElementCount(conditions) - 1 do 
  begin
    condItem := ElementByIndex(conditions, i);
    ctda := ElementBySignature(condItem, 'CTDA');
    if not Assigned(ctda) then ctda := condItem;

    funcStr := GetElementEditValues(ctda, 'Function');
    typ := GetElementNativeValues(ctda, 'Type');
    compVal := GetElementNativeValues(ctda, 'Comparison Value');
    
    DebugLog('  [Cond ' + IntToStr(i) + '] Func: "' + funcStr + '" | Type: ' + IntToStr(typ) + ' | Val: ' + FloatToStr(compVal));

    // SKIP: Run On Target (Bit 1)
    if (typ and 2) > 0 then begin
       DebugLog('    -> Skipping (Run On Target)');
       Continue;
    end;

    // PARAM 1
    param1 := ElementByName(ctda, 'Parameter #1');
    if not Assigned(param1) then param1 := ElementByIndex(ctda, 5);

    // CASE 1: GetIsID
    if funcStr = 'GetIsID' then 
    begin
      if (Abs(compVal - 1.0) < 0.01) then
      begin
        DebugLog('    -> Found GetIsID. Checking Actor...');
        sVal := GetVoiceTypeFromNPC(LinksTo(param1));
        if sVal <> '' then begin
           sl.Add(sVal);
           DebugLog('    -> SUCCESS: Added Voice: ' + sVal);
        end else begin
           DebugLog('    -> FAIL: Could not extract voice from Actor ID.');
        end;
      end;
    end
      
    // CASE 2: GetIsVoiceType
    else if funcStr = 'GetIsVoiceType' then
    begin
       if (Abs(compVal - 1.0) < 0.01) then
       begin
         sVal := GetVoiceTypeName(param1);
         if sVal <> '' then begin
            sl.Add(sVal);
            DebugLog('    -> SUCCESS: Added VTYP: ' + sVal);
         end;
       end;
    end
      
    // CASE 3: GetIsSex
    else if funcStr = 'GetIsSex' then 
    begin
      if GetEditValue(param1) = 'Male' then 
         GetStandardVoicesFromSex(sl, True)
      else 
         GetStandardVoicesFromSex(sl, False);
    end;
  end;
    
  lstVoiceTypes.AddStrings(sl);
  sl.Free;
end;


function Initialize: integer;
begin
  bDebug := True; 

  slExport := TStringList.Create;
  slExport.Add('game_id,voice_id,model,out_path,text');
  slVoiceTypes := TStringList.Create;
  slVoiceTypes.Sorted := True;
  slVoiceTypes.Duplicates := dupIgnore;
  slIDs := TStringList.Create;
  
  if bDebug then AddMessage('Initializing Export Script 2.7...');
end;


function Process(e: IInterface): Integer;
var
  dial, responses, response, masterFile: IInterface;
  i, j: integer;
  line, fname, voicename, path, model, voiceTypeStr: string;
begin
  if Signature(e) <> 'INFO' then Exit;

  if bDebug then AddMessage('--------------------------------------------------');
  if bDebug then AddMessage('Processing INFO: ' + IntToHex(FormID(e), 8));

  slVoiceTypes.Clear;
  slIDs.Clear;
  
  ParseConditions(ElementByName(e, 'Conditions'), slVoiceTypes, slIDs);
  
  if slVoiceTypes.Count = 0 then begin
    if bDebug then AddMessage('FALLBACK: No valid voices found in conditions. Exporting ALL.');
    GetStandardVoicesFromSex(slVoiceTypes, True);
    GetStandardVoicesFromSex(slVoiceTypes, False);
  end;
  
  dial := LinksTo(ElementByName(e, 'Topic'));
  responses := ElementByName(e, 'Responses');
  masterFile := MasterOrSelf(e);
  
  for i := 0 to Pred(ElementCount(responses)) do begin
    response := ElementByIndex(responses, i);
    
    fname := InfoFileName(
      GetFileName(masterFile),
      FormID(e),
      GetElementEditValues(response, 'TRDT\Response number')
    );

    for j := 0 to Pred(slVoiceTypes.Count) do begin
      voiceTypeStr := slVoiceTypes[j];
      
      if voiceTypeStr <> '' then
        begin
        voicename := 'Sound\Voice\' + GetFileName(masterFile) + '\' + voiceTypeStr + '\' + fname + '.fuz';
        model := 'sk_' + LowerCase(voiceTypeStr);
        path := 'Data\' + voicename;
        
        line :=
          'skyrim,' + 
          voiceTypeStr + ',' + 
          model + ',' +
          path + ',' +
          '"' + StringReplace(GetElementEditValues(response, 'NAM1'), '"', '''', [rfReplaceAll, rfIgnoreCase]) + '"';
      
        slExport.Add(line);
        end;
    end;
  end;
end;


function Finalize: integer;
var
  dlgSave: TSaveDialog;
begin
  if slExport.Count > 1 then begin
    dlgSave := TSaveDialog.Create(nil);
    dlgSave.Options := dlgSave.Options + [ofOverwritePrompt];
    dlgSave.Filter := 'CSV files (*.csv)|*.csv';
    dlgSave.InitialDir := DataPath;
    dlgSave.FileName := 'skyrimDialogueExport.csv';
    if dlgSave.Execute then begin
      AddMessage('Saving ' + dlgSave.FileName);
      slExport.SaveToFile(dlgSave.FileName);
    end;
    dlgSave.Free;
  end;
  slExport.Free;
  slVoiceTypes.Free;
  slIDs.Free;
end;

end.

 

 

 

Couple of things to note, since I'm not trying to spend a bunch of time on this script:

  • It doesn't check faction conditions. This shouldn't be hard, but I didn't bother. 
  • It DOES check NPC sex conditions, but if it's essentially Player.IsMale, it will add ALL male voice types. You can add a manual check for that condition though
  • I hardcoded voice types I found off the wiki. No clue how you personally handle unique voice types like Cicero so I assumed this is fine
  • Guards add all voice types. I think you can limit this though? I've only heard a handful of voices from guards, but wasn't 100% sure so. 

 

In other words, it's not perfect but I've included tons of debug printing and what not to make it super easy to improve the script. 

Edit: Btw, I plan on releasing a new slavery mod soon. Could you share the quick steps of using that voice generator on these files to save me some time? 

 

I plan to just do exactly what you're doing for my mod. Export voices, generate. I'm assuming this is done in one big batch..? 

I've been testing dialogue export scripts, including yours, I'm running into some oddities.

 

With Doublecheeseburger's Dialogue Export Script, TheAncientProfession will generate 45815 lines of dialogue

With you script it will generate 65279 lines of dialogue

And with the Oblivion Dialogue Export Script133320 lines of dialogue

 

Those seem like waaaay big numbers.

 

I've been using Doublecheeseburger's script for awhile now and the main issue I've been having with it is due to factions and quest aliases generating dialogue bloat.

 

Factions:

In the vanilla game, only FemaleNord, MaleGuard, and MaleNord commander can be guards and their dialogue conditions generally just say something like "has faction guards = 1" or something and isn't associated with a voice type or particular NPC.  So since there's no particular voice condition for them, the dialogue export script will generate dialogue for all available voice types which leads to massive dialogue bloat

 

Aliases:

This happens in scenes a lot.  I don't think I've ever really seen scene dialogue with individual conditions on the speaker, nor do there seem to be conditions on the controlling quest.  Instead, I'm guessing authors use scripts to fill aliases, which I would guess that the dialogue export script cannot detect.  So you end up with a particular line of dialogue that doesn't appear to have any voice conditions associated with them so the script will generate them for all possible voice types.

 

 

If you're still working on a dialogue export script, may I ask you to modify Doublecheeseburger's script to cross reference vanilla factions for potential voice types?  Lots of mods might write dialogue for bandits, for example, and if the script could generate voice files only for potential vanilla bandit voice types, that could cut down on a great deal of bloat.  And also, if you could get aliases to work properly, that would be wonderful too but I'm guessing that would not be easy.  But thankfully bloat only happens for scene dialogue and those can be identified and removed manually.

 

 

Posted (edited)
1 hour ago, kamithemoon said:

I've been testing dialogue export scripts, including yours, I'm running into some oddities.

 

With Doublecheeseburger's Dialogue Export Script, TheAncientProfession will generate 45815 lines of dialogue

With you script it will generate 65279 lines of dialogue

And with the Oblivion Dialogue Export Script133320 lines of dialogue

 

Those seem like waaaay big numbers.

 

I've been using Doublecheeseburger's script for awhile now and the main issue I've been having with it is due to factions and quest aliases generating dialogue bloat.

 

Factions:

In the vanilla game, only FemaleNord, MaleGuard, and MaleNord commander can be guards and their dialogue conditions generally just say something like "has faction guards = 1" or something and isn't associated with a voice type or particular NPC.  So since there's no particular voice condition for them, the dialogue export script will generate dialogue for all available voice types which leads to massive dialogue bloat

 

Aliases:

This happens in scenes a lot.  I don't think I've ever really seen scene dialogue with individual conditions on the speaker, nor do there seem to be conditions on the controlling quest.  Instead, I'm guessing authors use scripts to fill aliases, which I would guess that the dialogue export script cannot detect.  So you end up with a particular line of dialogue that doesn't appear to have any voice conditions associated with them so the script will generate them for all possible voice types.

 

 

If you're still working on a dialogue export script, may I ask you to modify Doublecheeseburger's script to cross reference vanilla factions for potential voice types?  Lots of mods might write dialogue for bandits, for example, and if the script could generate voice files only for potential vanilla bandit voice types, that could cut down on a great deal of bloat.  And also, if you could get aliases to work properly, that would be wonderful too but I'm guessing that would not be easy.  But thankfully bloat only happens for scene dialogue and those can be identified and removed manually.

 

 

Hey my script isn't really intended to be used as is - just an example. The output path isn't even correct. It should be Branch_Topic_ID. Just there to learn from. 

 

In somewhat related news, I've been considering working on a new voice generator from finetuning, but the models are getting insane in size and still have issues with expressing emotion (Only tried with MaleNord). So now I'm experimenting with voice cloning models with SOME success.

 

So far this is the best model I've found for cloning: https://github.com/index-tts/index-tts  - https://huggingface.co/IndexTeam/IndexTTS-2

 

Great voice modulation with very few artifacts - but no emotional context creates somewhat out-of-place voices. so my plan now is to maybe fine tune an LLM or just hard-code some emotion controller for each dialogue line to use in the audio context. 

 

Or maybe we can request that mod authors tag text for emotional effect? Like SAD, ANGRY - etc (Or use built in expressions?).

 

Idk, my ramble is over. Point is, we can do better then that SnythX mod and still run locally. 

 

Here's a demo of IndexTTS-2: https://huggingface.co/spaces/IndexTeam/IndexTTS-2-Demo
 

Edited by asdt123123
Posted (edited)
2 hours ago, asdt123123 said:

Hey my script isn't really intended to be used as is - just an example. The output path isn't even correct. It should be Branch_Topic_ID. Just there to learn from. 

 

In somewhat related news, I've been considering working on a new voice generator from finetuning, but the models are getting insane in size and still have issues with expressing emotion (Only tried with MaleNord). So now I'm experimenting with voice cloning models with SOME success.

 

So far this is the best model I've found for cloning: https://github.com/index-tts/index-tts  - https://huggingface.co/IndexTeam/IndexTTS-2

 

Great voice modulation with very few artifacts - but no emotional context creates somewhat out-of-place voices. so my plan now is to maybe fine tune an LLM or just hard-code some emotion controller for each dialogue line to use in the audio context. 

 

Or maybe we can request that mod authors tag text for emotional effect? Like SAD, ANGRY - etc (Or use built in expressions?).

 

Idk, my ramble is over. Point is, we can do better then that SnythX mod and still run locally. 

 

Here's a demo of IndexTTS-2: https://huggingface.co/spaces/IndexTeam/IndexTTS-2-Demo
 

Authors do tag text for emotional intensity.  It goes from 0 - 100 and has Happy, Angry, Surprised, Fearful, Neutral, and Confused.  The original dialogue extraction scripts were built for xVASynth though so the emotional bit wasn't used.

 

I've been building voices oldskool with the Tortoise + RVC combo.  It works well for the most part and can even generate some emotion though not much.  But the two biggest hurdles I've been facing are dialogue bloat, which I mentioned earlier, and context.  For context, with the Animal Research voices I made, I checked each one for quality and they sound fine individually without context.  But in game is when I find out things do not match.  For example, something simple like "Yes." might appear a dozen times or so but all I see is "Yes." and have no idea what the context is so in game it might come out super weird.

 

 

Oh, I must say, I'm liking IndexTTS-2.  Do you happen to have a guide on how to install and use it locally?  I was following this one but I keep running into an issue with the initial download of "This repository exceed its LFS budget"

Edited by kamithemoon
Posted (edited)
2 hours ago, kamithemoon said:

Authors do tag text for emotional intensity.  It goes from 0 - 100 and has Happy, Angry, Surprised, Fearful, Neutral, and Confused.  The original dialogue extraction scripts were built for xVASynth though so the emotional bit wasn't used.

 

I've been building voices oldskool with the Tortoise + RVC combo.  It works well for the most part and can even generate some emotion though not much.  But the two biggest hurdles I've been facing are dialogue bloat, which I mentioned earlier, and context.  For context, with the Animal Research voices I made, I checked each one for quality and they sound fine individually without context.  But in game is when I find out things do not match.  For example, something simple like "Yes." might appear a dozen times or so but all I see is "Yes." and have no idea what the context is so in game it might come out super weird.

 

 

Oh, I must say, I'm liking IndexTTS-2.  Do you happen to have a guide on how to install and use it locally?  I was following this one but I keep running into an issue with the initial download of "This repository exceed its LFS budget"

Yeah their LFS budget has been cooked for days. On Github just manually click Code -> Download ZIP. 

When you're installing, don't do uv sync --all-extras. Deepspeed is buggy as fuck on Windows. Just do uv sync --extra webui

Then get the model:

uv tool install "huggingface-hub[cli,hf_xet]"

hf download IndexTeam/IndexTTS-2 --local-dir=checkpoints

And that's all you need to do. 

Edit: Forgot to mention. When you're using the mode - set the temperature low to like 0.4 if not lower. Use emotion vectors. And with your input audio, make absolute sure it doesn't contain pauses. This model fucking LIKES pausing randomly in audio. If your input audio has pauses (from commas, etc), cut it out even if it sounds a bit weird. 

 

Here's my super basic script for automating voice generation. I only included a few races. Extract this in the same folder as index-tts. Run it using uv run skyrim_voice_generator.py

This is just a quick thing I put together so don't expect greatness. If you manage to get better results, SHARE 🙏

Make sure you reformat the .wav files to what skyrim expects: mono 41k hz 16bit

skyrim_voice_generator.zip

Edited by asdt123123
Posted
48 minutes ago, asdt123123 said:

Yeah their LFS budget has been cooked for days. On Github just manually click Code -> Download ZIP. 

Shoot, I should have mentioned that I already tried that.  The file is only 31.8 mb large and the extracted files seem to be broken.  For example, all the example audio is only 131 bytes large and contain no actual audio.  Same is true with your original link to Index TTS.

 

Dang, I kinda figured it was a github issue.  I guess all I can do is wait until the new month and see if his tokens or whatever resets itself.

Posted
57 minutes ago, kamithemoon said:

Shoot, I should have mentioned that I already tried that.  The file is only 31.8 mb large and the extracted files seem to be broken.  For example, all the example audio is only 131 bytes large and contain no actual audio.  Same is true with your original link to Index TTS.

 

Dang, I kinda figured it was a github issue.  I guess all I can do is wait until the new month and see if his tokens or whatever resets itself.

You don't need any of those audio files. They're purely for testing their examples. The program works flawlessly without them. 

Posted (edited)
2 hours ago, asdt123123 said:

You don't need any of those audio files. They're purely for testing their examples. The program works flawlessly without them. 

Sweet! Using your guide, I got it working.

 

Next question, do you have a script for extracting voice files for a mod in a format that IndexTTS can use to batch generate?  Originally, I was using the Audiobook maker from Jarod's Journey and a script written by @Killer905 to batch generate individual audio files and rename them into the format needed for the quest.  Then I'd use RunaLip to make the fuz files.

Edited by kamithemoon
Posted

Just thought I hop onto the train and try to get this TTS Model Working to!  the last voicefiles sound amazing, so yeah atm I'm trying to download the model and get it working

 

Posted
18 hours ago, asdt123123 said:

Here's my super basic script for automating voice generation. I only included a few races. Extract this in the same folder as index-tts. Run it using uv run skyrim_voice_generator.py

This is just a quick thing I put together so don't expect greatness. If you manage to get better results, SHARE 🙏

Make sure you reformat the .wav files to what skyrim expects: mono 41k hz 16bit

skyrim_voice_generator.zip 1.65 MB · 1 download

Sweet, I got it working mostly and figured out the file you need is lines.csv.  But next question, what type of formatting do you need to read lines.csv.  I tried the default formatting for xVASynth and also with just file name and text and neither of them loaded correctly, or else my output was 0 for a FemaleEventoned voice files.

Posted (edited)
7 hours ago, bigbasi said:

Just thought I hop onto the train and try to get this TTS Model Working to!  the last voicefiles sound amazing, so yeah atm I'm trying to download the model and get it working

 

I think we could all collaborate and manage to make a really good voice system based on this model. 

 

If you check out my mini-project, I was messing with emotion vectors based on the comment. So "Hey you!!" is angry, "Hey you!" is mean, "Who are you?" is question, etc. Super simple - but it's difficult to derive emotion from text lol. 

 

Like, "Hey you!" - could be a friendly greeting. Or it could be a - hey stop right there criminal type thing lol.  

 

Just to put some more context into the code:

In tts.infer - you'll see a "do_sample" variable (In the webUI it's named gpt2 I think?). This toggles the sampling to use greedy search instead. So instead of using an LLM,  for every step of generation, it strictly picks the single token with the highest probability. 

 

So that might be something you want to mess with. You can see I set the temperature to 0.4 and enabled sampling. 


Emotion vectors:

Here's the emotion vectors:

  1. Happy - vec1
  2. Angry - vec2
  3. Sad - vec3
  4. Fear / Afraid - vec4
  5. Disgust - vec5
  6. Melancholic / Depressed - vec6
  7. Surprise - vec7
  8. Calm / Neutral - vec8


so [1.0, 0, 0, 0, 0, 0, 0, 1.0] = Happy at 1, Calm at 1. I haven't tried values over 1, I assume they're capped there since it gets pretty wild at 1 lol
 

 In the inference code (normalize_emo_vec function), the model applies a "bias" to these inputs before generating audio. This means setting a slider to "1.0" does not affect the model equally for every emotion. That's why I made a separate config for each voice.
 

The model explicitly dampens Surprise and Calm:

  •     Happy/Disgust/Melancholic are multiplied by ~0.94.
  •     Angry is multiplied by 0.875.
  •     Sad/Fear are passed through fully (1.0).
  •     Surprise is multiplied by 0.68.
  •     Calm is multiplied by 0.56.

This is likely more of a normalization then anything but it's still something to note. 

 

If you do not include an emotion vector, the model will derive emotions from the input voice you gave it. SO "tts.infer(spk_audio_prompt='examples/voice_01.wav', text=text, output_path="gen.wav", verbose=True)" = Tells the model to derrive the emotion data from the input audio.

You COULD possibly use this to improve the output more..? I went straight to emotion vectors and never tested this. Obviously you'd need different input voices like: MaleNord_mad.wav, MaleNord_happy.wav - etc. 

Tokenization: 

I noticed the model has some strange subword tokenization. Might be something worth looking into if you experience issues. I say that purely because this is a Chinese made model, so might have issues with English? 

Here's an example:
That = _That
Stomach = _S to ma ch

This is just their BPE model at work basically, so theoretically could be absolutely fine. I'm just noting it because again - this is a Chinese trained model. So MIGHT have to include specific pronunciation for certain stuff if we have problems. 

Final notes:

I set max_text_tokens_per_segment to 600 to basically force the model to use more context in simple terms. It'll help with flow, and likely prevent weird pauses more. Should be low enough to avoid artifacts, and not use too much extra VRAM. 

 

I haven't touched top_p and top_k. I usually use 0.8 or 0.9 in LLMs and never touch top_k. I only really mess with temperature. Lower the temp, more deterministic which is largely what we want here. 

 

 

2 hours ago, kamithemoon said:

Sweet, I got it working mostly and figured out the file you need is lines.csv.  But next question, what type of formatting do you need to read lines.csv.  I tried the default formatting for xVASynth and also with just file name and text and neither of them loaded correctly, or else my output was 0 for a FemaleEventoned voice files.

It will parse those cvs files outputted from the script we wrote for xEdit->xVASynth. Doesn't take anything custom. 

 

If you have trouble, just ask ChatGPT or something to fix the formatting and give it the python script and first 5 lines of your lines.cvs. 

Edited by asdt123123
Posted (edited)
28 minutes ago, asdt123123 said:

I think we could all collaborate and manage to make a really good voice system based on this model. 

 

If you check out my mini-project, I was messing with emotion vectors based on the comment. So "Hey you!!" is angry, "Hey you!" is mean, "Who are you?" is question, etc. Super simple - but it's difficult to derive emotion from text lol. 

 

Like, "Hey you!" - could be a friendly greeting. Or it could be a - hey stop right there criminal type thing lol.  

 

Just to put some more context into the code:

In tts.infer - you'll see a "do_sample" variable (In the webUI it's named gpt2 I think?). This toggles the sampling to use greedy search instead. So instead of using an LLM,  for every step of generation, it strictly picks the single token with the highest probability. 

 

So that might be something you want to mess with. You can see I set the temperature to 0.4 and enabled sampling. 


Emotion vectors:

Here's the emotion vectors:

  1. Happy - vec1
  2. Angry - vec2
  3. Sad - vec3
  4. Fear / Afraid - vec4
  5. Disgust - vec5
  6. Melancholic / Depressed - vec6
  7. Surprise - vec7
  8. Calm / Neutral - vec8


so [1.0, 0, 0, 0, 0, 0, 0, 1.0] = Happy at 1, Calm at 1. I haven't tried values over 1, I assume they're capped there since it gets pretty wild at 1 lol
 

 In the inference code (normalize_emo_vec function), the model applies a "bias" to these inputs before generating audio. This means setting a slider to "1.0" does not affect the model equally for every emotion. That's why I made a separate config for each voice.
 

The model explicitly dampens Surprise and Calm:

  •     Happy/Disgust/Melancholic are multiplied by ~0.94.
  •     Angry is multiplied by 0.875.
  •     Sad/Fear are passed through fully (1.0).
  •     Surprise is multiplied by 0.68.
  •     Calm is multiplied by 0.56.

This is likely more of a normalization then anything but it's still something to note. 

 

If you do not include an emotion vector, the model will derive emotions from the input voice you gave it. SO "tts.infer(spk_audio_prompt='examples/voice_01.wav', text=text, output_path="gen.wav", verbose=True)" = Tells the model to derrive the emotion data from the input audio.

You COULD possibly use this to improve the output more..? I went straight to emotion vectors and never tested this. Obviously you'd need different input voices like: MaleNord_mad.wav, MaleNord_happy.wav - etc. 

Tokenization: 

I noticed the model has some strange subword tokenization. Might be something worth looking into if you experience issues. I say that purely because this is a Chinese made model, so might have issues with English? 

Here's an example:
That = _That
Stomach = _S to ma ch

This is just their BPE model at work basically, so theoretically could be absolutely fine. I'm just noting it because again - this is a Chinese trained model. So MIGHT have to include specific pronunciation for certain stuff if we have problems. 

Final notes:

I set max_text_tokens_per_segment to 600 to basically force the model to use more context in simple terms. It'll help with flow, and likely prevent weird pauses more. Should be low enough to avoid artifacts, and not use too much extra VRAM. 

 

I haven't touched top_p and top_k. I usually use 0.8 or 0.9 in LLMs and never touch top_k. I only really mess with temperature. Lower the temp, more deterministic which is largely what we want here. 

 

 

It will parse those cvs files outputted from the script we wrote for xEdit->xVASynth. Doesn't take anything custom. 

 

If you have trouble, just ask ChatGPT or something to fix the formatting and give it the python script and first 5 lines of your lines.cvs. 

 

This is exciting!  It took some tweaking but I have a batch generation for FemaleEventoned for Animal Research going.  The initial output quality does seem less than what I get out of Tortoise but once it's done generating, I'll post process though RVC to see if that improves things.  At the very least, IndexTTS seems MUCH more consistent when generating dialogue and there's a lot less broken generations.  If nothing else, it could be a great fallback tool for generations that Tortoise has trouble with.

 

This tool and your script was quite easy to use so I would say that ultimately, I would want it in the hands of the mod authors.  The mod authors are the ones that understand the emotional context of their dialogue and would be best suited to generating dialogue that's suitable.  I'm looking forward to the day when new mods will come with pregenerated voices.

 

Before yesterday, I have never once used ChatGPT and was only vaguely aware of its existence.  But it's turned out to be an invaluable tool for troubleshooting crash logs.

 

Edited by kamithemoon
Posted
1 hour ago, kamithemoon said:

Hrm, RVC post processing makes it sound ALOT better.  Check out these 2 generations.  The 1st one is IndexTTS and the other is Tortoise.

RVC is for sure something to look into. I'm only concerned it will have a lot of trouble with like Nord voice types for example. We do still have the ability to finetune these models for specific characters, but finetunes/loras will still have a lot of issues with emotion expression. And agan, the nord voice is pretty difficult to work with. I was actually amazed Index clones Nord voices so well. 

 

  • 1 month later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...