/// <summary> /// Initializes a new instance of the WaveInventoryCreator class. /// </summary> /// <param name="resultFile">The file name of the result files without extension name.</param> /// <param name="header">The wave info header.</param> /// <param name="namedUnitTypeId">The Dictionary which key is unit name and the value is index type id.</param> /// <param name="phoneIdMap">Phone id map .</param> /// <param name="millisecondPerFrame">Millisecond per frame.</param> /// <param name="keepLeftRightFrameMargin">Keep left right frame margin.</param> /// <param name="obfuscationMethod">The given method to perform obfuscation for the wave data.</param> public WaveInventoryCreator(string resultFile, WaveInfoHeader header, IDictionary<string, int> namedUnitTypeId, IDictionary<string, int> phoneIdMap, int millisecondPerFrame, int keepLeftRightFrameMargin, ObfuscationMethod obfuscationMethod) { if (string.IsNullOrEmpty(resultFile)) { throw new ArgumentNullException("resultFile"); } if (header == null) { throw new ArgumentNullException("header"); } if (namedUnitTypeId == null) { throw new ArgumentNullException("namedUnitTypeId"); } if (phoneIdMap == null) { throw new ArgumentNullException("phoneIdMap"); } if (obfuscationMethod == null) { throw new ArgumentNullException("obfuscationMethod"); } if (millisecondPerFrame <= 0) { throw new ArgumentException("millisecondPerFrame"); } if (keepLeftRightFrameMargin < 0) { throw new ArgumentException("keepLeftRightFrameMargin"); } _obfuscationMethod = obfuscationMethod; _header = header; _header.Validate(); _ccMarginLength = (int)(_header.CrossCorrelationMarginLength * 0.001f * _header.SamplesPerSecond); _fsMarginLength = (int)(keepLeftRightFrameMargin * millisecondPerFrame * 0.001f * _header.SamplesPerSecond); _namedUnitTypeId = namedUnitTypeId; _phoneIdMap = phoneIdMap; _indexingFile = new UnitIndexingFile(namedUnitTypeId); _fileName = resultFile; Helper.EnsureFolderExistForFile(_fileName); string fullFileName; switch (_header.Compression) { case WaveCompressCatalog.Unc: fullFileName = _fileName.AppendExtensionName(FileExtensions.WaveInventory); break; default: throw new NotSupportedException("Only Unc is supported"); } _fileStream = new FileStream(fullFileName, FileMode.Create); _writer = new BinaryWriter(_fileStream); _voiceFontHeader = new VoiceFontHeader { FileTag = 0x45564157, // "WAVE" FormatTag = VoiceFontTag.FmtIdUncompressedWaveInventory, Version = 0, Build = 0, DataSize = 0, }; _voiceFontHeader.Save(_writer); _dataOffset = _writer.BaseStream.Position; _millisecondPerFrame = millisecondPerFrame; LogFile = string.Empty; }
/// <summary> /// Update the ACD file. /// When we split the WVE, if the sub WVE data can't be divided exactly using a frame data size. /// To keep consistent with compressed font, we need add some data into the sub WVE. /// After this operation, we need update the frame index in UNT, all freame index behind of the added frame will be increase some frame offset. /// However, when we get the frame information in ACD, we need using the frame index in unt file. /// So to keep consistent, we need insert some frame in ACD file the same time. /// </summary> /// <param name="indexFile">Unit index data.</param> /// <param name="sentencFrameUpdateList">Frame update list.</param> /// <param name="acdFilePath">Old acd file path.</param> /// <param name="newAcdFilePath">New acd file path.</param> private static void UpdateACDFile(UnitIndexingFile indexFile, Dictionary<string, uint> sentencFrameUpdateList, string acdFilePath, string newAcdFilePath) { if (sentencFrameUpdateList.Where(k => k.Value != 0).ToList().Count != 0 && !File.Exists(acdFilePath)) { PrintLog(string.Format("Please check your font, the ACD file need to be update!"), true); } if (File.Exists(acdFilePath)) { ACDData acdData = null; using (FileStream acdFile = File.Open(acdFilePath, FileMode.Open)) using (ACDDataReader acdReader = new ACDDataReader(new BinaryReader(acdFile))) { acdData = acdReader.ReadACDData(); } foreach (var item in sentencFrameUpdateList) { // Get all candidate which are got from the specfied sentenc and sorted by frame index. List<WaveCandidateInfo> endSenteceWaveInfo = indexFile.WaveCandidates.SelectMany(c => c.Value.Where(p => p.Value.SentenceId == item.Key)) .Select(i => i.Value).SortBy(k => k.FrameIndexInSentence).ToList(); // Get the end candiate in the specifid sentence. WaveCandidateInfo firstWaveCandidate = endSenteceWaveInfo[0]; uint frameStart = (uint)(firstWaveCandidate.FrameIndex - firstWaveCandidate.FrameIndexInSentence); if (frameStart > 0 && acdData != null) { if (acdData.LpccData != null) { AddAcdTableItem(acdData.LpccData, frameStart, item.Value); } if (acdData.RealLpccData != null) { AddAcdTableItem(acdData.RealLpccData, frameStart, item.Value); } if (acdData.F0Data != null) { AddAcdTableItem(acdData.F0Data, frameStart, item.Value); } if (acdData.GainData != null) { AddAcdTableItem(acdData.GainData, frameStart, item.Value); } if (acdData.PowerData != null) { AddAcdTableItem(acdData.PowerData, frameStart, item.Value); } if (acdData.PitchMarkerData != null) { AddAcdTableItem(acdData.PitchMarkerData, frameStart, item.Value); } } ACDDataWriter writer = new ACDDataWriter(); writer.Write(acdData, newAcdFilePath); } } }
/// <summary> /// Update the indexing file based pm tje candiate info. /// </summary> /// <param name="indexFile">The index file.</param> /// <param name="sentenceFrameUpdateList">SentenceOrder list.</param> private static void UpdateIndexingFile(UnitIndexingFile indexFile, Dictionary<string, uint> sentenceFrameUpdateList) { // update all global index. indexFile.WaveCandidates.Values.ForEach(p => p.ForEach(w => { WaveCandidateInfo wunt = w.Value; if (sentenceFrameUpdateList.ContainsKey(wunt.SentenceId)) { wunt.FrameIndex += sentenceFrameUpdateList[wunt.SentenceId]; } else { throw new Exception(string.Format("Please check the code, some sentence missed. senteceID {0}", wunt.SentenceId)); } })); }
/// <summary> /// Start the pruning. /// </summary> /// <param name="fontPath">Voice font path, like .\1033 .</param> /// <param name="configFile">Config file which will tell how many sentence will be include in each sub wve.</param> /// <param name="bFillData">Whether fill data if the sub WVE can't be exact division.</param> /// <param name="outputFolder">Target directory.</param> /// <returns>New WVE File path List.</returns> public static List<string> InventorySplit(string fontPath, string configFile, bool bFillData, string outputFolder) { string unitfile = fontPath + ".unt"; string wihFile = fontPath + ".wih"; string wveFile = fontPath + ".wve"; string outputUnit = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(unitfile) + ".Splited.unt"); PrintLog("Start to update split the WVE file...", true); // spliet the file. List<string> newWVEFileList = new List<string>(); // Load the WIH file. WaveInfoHeader header = new WaveInfoHeader(); header.Load(wihFile); const uint CompressFrameSize = 640; Dictionary<string, uint> untFrameUpdateList = new Dictionary<string, uint>(); Dictionary<string, uint> acdFrameUpdateList = new Dictionary<string, uint>(); uint curFillFrameCount = 0; Dictionary<string, int> namedUnitTypeId = new Dictionary<string, int>(); Dictionary<int, WaveCandidateInfo> updatedCandidates = new Dictionary<int, WaveCandidateInfo>(); using (UnitIndexingFile indexFile = new UnitIndexingFile(namedUnitTypeId)) { indexFile.Load(unitfile); uint bytesPerFrame = indexFile.SamplePerFrame * header.BytesPerSample; uint compressFrameCount = CompressFrameSize / bytesPerFrame; // Get all sentence id information from the index file canidate, and save them in a sorted dictionary. // Because the sentece is sorted by a sorted dictionary in Font traing process. List<string> sentencIDs = indexFile.WaveCandidates.SelectMany(c => c.Value.Select(k => k.Value.SentenceId).Distinct()).Distinct().ToList(); Dictionary<string, int> dic = sentencIDs.Select((v, i) => new { Value = v, Index = i }).ToDictionary(p => p.Value, p => p.Index); SortedDictionary<string, int> sortedSentenceDic = new SortedDictionary<string, int>(dic); List<string> orderedSentenceIDs = sortedSentenceDic.Select(e => e.Key).ToList(); // Get the confing data. List<uint> sentencCountList = ReadConfigFile(configFile, (uint)orderedSentenceIDs.Count); using (FileStream readerStream = File.Open(wveFile, FileMode.Open)) { BinaryReader reader = new BinaryReader(readerStream); // load header VoiceFontHeader fontHeader = new VoiceFontHeader(); fontHeader.Load(reader); PrintLog("[Totoal Frame in each new VWF file.]", true); // the data offset from the header long dataOffSet = reader.BaseStream.Position; uint outputFileSuffix = 0; uint readFrameNumber = 0; int curSplitSegmentIndex = 0; for (int index = 0; index <= orderedSentenceIDs.Count; index++) { if (index < sentencCountList[curSplitSegmentIndex]) { untFrameUpdateList.Add(orderedSentenceIDs[index], curFillFrameCount); continue; } curSplitSegmentIndex++; string newWVEFileName = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(wveFile) + "_" + outputFileSuffix.ToString() + ".wve"); PrintLog(string.Format("{0} Frame Update Count: {1}", Path.GetFileName(newWVEFileName), curFillFrameCount), true); using (FileStream wveFileStream = File.Open(newWVEFileName, FileMode.Create)) { BinaryWriter wveWriter = new BinaryWriter(wveFileStream); fontHeader.Save(wveWriter); long writerOffSet = wveWriter.BaseStream.Position; // calculate lenth long length = 0; uint frameCount = 0; if (index == orderedSentenceIDs.Count) { length = reader.BaseStream.Length - reader.BaseStream.Position; frameCount = (uint)(length / bytesPerFrame); } else { // Get all candidate which are got from the specfied sentenc and sorted by frame index. string endSentenceID = orderedSentenceIDs[index]; List<WaveCandidateInfo> endSenteceWaveInfo = indexFile.WaveCandidates.SelectMany(c => c.Value.Where(p => p.Value.SentenceId == endSentenceID)) .Select(i => i.Value).SortBy(k => k.FrameIndexInSentence).ToList(); // Get the end candiate in the specifid sentence. WaveCandidateInfo firstWaveCandidate = endSenteceWaveInfo[0]; frameCount = firstWaveCandidate.FrameIndex - firstWaveCandidate.FrameIndexInSentence - readFrameNumber; length = bytesPerFrame * frameCount; readFrameNumber = firstWaveCandidate.FrameIndex - firstWaveCandidate.FrameIndexInSentence; } // we need calculate how many bit will be filled into the WVE when it will be commpressed. // 640 bit will be used in the compress tool, so we will reference this. uint byteGap = (uint)((CompressFrameSize - (length % CompressFrameSize)) % CompressFrameSize); uint updateFrameCount = (byteGap / bytesPerFrame) % compressFrameCount; curFillFrameCount += updateFrameCount; reader.BaseStream.Seek(dataOffSet, SeekOrigin.Begin); byte[] data = reader.ReadBytes((int)length); wveWriter.Write(data); // fill data if the length can't be exact division. if (bFillData) { FillSilenceData(wveWriter, byteGap); } // save the header file fontHeader.DataSize = (ulong)(wveWriter.BaseStream.Position - writerOffSet); SaveFontHeader(fontHeader, wveWriter); dataOffSet = reader.BaseStream.Position; PrintLog(string.Format("{0} Total Frame Count: {1} + {2}", Path.GetFileName(newWVEFileName), frameCount, updateFrameCount.ToString()), true); PrintLog(string.Empty, true); // recode the new file path. newWVEFileList.Add(newWVEFileName); if (index != orderedSentenceIDs.Count) { acdFrameUpdateList.Add(orderedSentenceIDs[index], curFillFrameCount); } } header.Save(Path.ChangeExtension(newWVEFileName, "wih")); outputFileSuffix++; if (index != orderedSentenceIDs.Count) { index--; } } } // update ACD file. PrintLog("Update ACD file!", true); UpdateACDFile(indexFile, acdFrameUpdateList, fontPath + ".acd", Path.ChangeExtension(outputUnit, "acd")); // update index file PrintLog("Update UNT file!", true); UpdateIndexingFile(indexFile, untFrameUpdateList); indexFile.Save(outputUnit); } PrintLog("Split successfully!", true); return newWVEFileList; }
/// <summary> /// Compares two UNT files by ignoring builder number. /// </summary> /// <param name="left">The left file name of UNT file.</param> /// <param name="right">The right file name of UNT file.</param> /// <returns>True if the two UNT file is equal, otherwise false.</returns> public static bool Compare(string left, string right) { using (UnitIndexingFile leftFile = new UnitIndexingFile(null)) { leftFile.Load(left); using (UnitIndexingFile rightFile = new UnitIndexingFile(null)) { rightFile.Load(right); leftFile.BuildNumber = rightFile.BuildNumber; string tempFile = left + Path.GetRandomFileName(); try { leftFile.Save(tempFile); return Helper.CompareBinary(tempFile, right); } finally { Helper.SafeDelete(tempFile); } } } }
/// <summary> /// Create domain index file. /// </summary> /// <param name="scriptFile">Script file.</param> /// <param name="domainList">Domain list.</param> /// <param name="uif">Name indexed unit features.</param> public void Create(XmlScriptFile scriptFile, DomainConfigList domainList, UnitIndexingFile uif) { // Parameters Validation if (scriptFile == null) { throw new ArgumentNullException("scriptFile"); } if (domainList == null) { throw new ArgumentNullException("domainList"); } if (uif == null) { throw new ArgumentNullException("uif"); } Dictionary<string, DomainIndexItem> items = new Dictionary<string, DomainIndexItem>(StringComparer.Ordinal); _language = scriptFile.Language; _tag = domainList.FontTag; Phoneme phoneme = Localor.GetPhoneme(_language); SliceData sliceData = Localor.GetSliceData(_language); foreach (ScriptItem scriptItem in scriptFile.Items) { if (!domainList.Contains(scriptItem.Id)) { continue; } Collection<TtsUnit> itemUnits = scriptItem.GetUnits(phoneme, sliceData); Collection<ScriptWord> allPronouncedNormalWords = scriptItem.AllPronouncedNormalWords; for (int i = 0; i < allPronouncedNormalWords.Count; i++) { ScriptWord word = allPronouncedNormalWords[i]; string text; if (domainList.Domain == ScriptDomain.Number) { text = GetNumberDomainWordText(word, scriptItem.Id, i, (domainList as NumberDomainConfigList).Digitals); } else if (domainList.Domain == ScriptDomain.Acronym) { text = GetAcronymDomainWordText(word, scriptItem.Id, i, (domainList as AcronymDomainConfigList).Acronyms); } else if (domainList.Domain == ScriptDomain.Letter) { // Use pronunciation phone ids as key text = GetPhoneIds(word); } else { text = word.Grapheme.ToUpperInvariant(); } if (items.ContainsKey(text) && domainList.Domain != ScriptDomain.Letter) { // Skip duplicate word, except Letter domain continue; } DomainIndexItem item = null; if (!items.ContainsKey(text)) { item = new DomainIndexItem(); item.Word = text; } else { item = items[text]; } bool skipped = false; Collection<TtsUnit> wordUnits = word.GetUnits(phoneme, sliceData); for (int wordUnitIndex = 0; wordUnitIndex < wordUnits.Count; wordUnitIndex++) { TtsUnit unit = wordUnits[wordUnitIndex]; FeatureDataItem featureItem = new FeatureDataItem(); int indexOfNonSilence = itemUnits.IndexOf(unit); Debug.Assert(indexOfNonSilence >= 0 && indexOfNonSilence < itemUnits.Count); int unitOffset = uif.SearchCandidateOffset(unit.MetaUnit.Name, scriptItem.Id, (uint)indexOfNonSilence); if (unitOffset == -1) { // Skip this word skipped = true; break; } if (item.FeatureItems.Count == wordUnitIndex) { featureItem.UnitIndexes.Add(unitOffset); item.FeatureItems.Add(featureItem); // [].UnitIndexes.Add(unitOffset); } else { item.FeatureItems[wordUnitIndex].UnitIndexes.Add(unitOffset); } } if (!skipped && !items.ContainsKey(item.Word)) { items.Add(item.Word, item); } } } _items = BuildHashTable(items.Values); }