/// <summary> /// 同じ口形状かつ LinkType.Normal のユニットが連続する場合の /// 接続ウェイト値の既定値を決定する。 /// </summary> /// <param name="before">先行ユニット。</param> /// <param name="after">後続ユニット。</param> /// <returns>接続ウェイト値の既定値。</returns> private static float DecideDefaultSameLipLinkWeight( LipSyncUnit before, LipSyncUnit after) { switch (after.LipId) { case LipId.I: return 1.0f; case LipId.U: return 0.9f; } return 0.7f; }
/// <summary> /// ユニットの最大開口の長さを算出する。 /// </summary> /// <param name="unit">ユニット。</param> /// <returns>最大開口の長さ。</returns> private decimal CalcMaxOpenLength(LipSyncUnit unit) => (CalcUnitLength(unit) - CalcOpenCloseLength(unit));
/// <summary> /// ユニットの開口開始位置から最大開口終了位置までの長さを算出する。 /// </summary> /// <param name="unit">ユニット。</param> /// <returns>開口開始位置から最大開口終了位置までの長さ。</returns> private decimal CalcUnitLength(LipSyncUnit unit) => (unit.LengthPercent / 100m);
/// <summary> /// 開口および閉口の長さを算出する。 /// </summary> /// <param name="unit">ユニット。</param> /// <returns>開口および閉口の長さ。</returns> private decimal CalcOpenCloseLength(LipSyncUnit unit) => (unit.LengthPercent * this.LinkLengthPercent / 10000);
/// <summary> /// 同じ口形状かつ LinkType.Normal のユニットが連続する場合の /// 接続ウェイト値を決定する。 /// </summary> /// <param name="before">先行ユニット。</param> /// <param name="after">後続ユニット。</param> /// <returns>接続ウェイト値。</returns> private float DecideSameLipLinkWeight( LipSyncUnit before, LipSyncUnit after) { return (this.SameLipLinkWeightDecider == null) ? DecideDefaultSameLipLinkWeight(before, after) : this.SameLipLinkWeightDecider(before, after); }
/// <summary> /// ユニットリストをキー領域に変換してタイムラインに追加する。 /// </summary> /// <param name="tlSet">追加先のタイムラインセット。</param> /// <param name="beginPlace">開始キー位置。</param> /// <param name="areaUnits">ユニットリスト。</param> /// <param name="prevUnit">先行発声ユニット。無いならば null 。</param> /// <param name="nextUnit">後続発声ユニット。無いならば null 。</param> /// <returns>後続のキー領域を開始すべきキー位置。</returns> private decimal AddAreaUnitList( TimelineSet tlSet, decimal beginPlace, IList<LipSyncUnit> areaUnits, LipSyncUnit prevUnit, LipSyncUnit nextUnit) { if (areaUnits.Count <= 0) { return beginPlace; } // 最初のユニットと口形状種別IDを取得 var unit = areaUnits[0]; var id = unit.LipId; var area = new TimelineKeyArea(); // 開口および閉口の長さを算出 var openCloseLen = CalcOpenCloseLength(unit); // 開始キーを追加 var openHalfPos = beginPlace + openCloseLen / 2; if (prevUnit != null && unit.LinkType != LinkType.Normal) { // 一旦口を(半分)閉じるならば開口時間の1/2まで経過してから開口開始 area.SetWeight(openHalfPos, 0); } else if (prevUnit != null && prevUnit.LipId == id) { // 直前と同じ口形状ならば開口時間の1/2まで経過してから開口開始 // 接続ウェイト値は直前の口形状の終端部で設定済み area.SetWeight(openHalfPos, 0); } else { area.SetWeight(beginPlace, 0); } // 最大開口開始キーを追加 var maxBeginPos = beginPlace + openCloseLen; area.SetWeight(maxBeginPos, 1); // 次の音開始位置(=最大開口終了位置)を算出 var linkPos = maxBeginPos + CalcMaxOpenLength(unit); // 最初のユニットをカット var units = areaUnits.Skip(1); // 次のユニットを取得 unit = units.FirstOrDefault(); // 最大開口終端位置追加済みフラグ bool maxEndAdded = false; // 長音部分を計算 if (unit != null && unit.LinkType == LinkType.LongSound) { // 次の音開始位置をシフト foreach ( var u in units.TakeWhile(u => u.LinkType == LinkType.LongSound)) { linkPos += CalcUnitLength(u); } // 長音終端キーを追加 area.SetWeight(linkPos, this.LongSoundLastWeight); maxEndAdded = true; // 長音部分をカット units = units.SkipWhile(u => u.LinkType == LinkType.LongSound); // 次のユニットを取得 unit = units.FirstOrDefault(); } // 促音部分を計算 if (unit != null && unit.LinkType == LinkType.Tsu) { // 次の音開始位置をシフト foreach ( var u in units.TakeWhile(u => u.LinkType == LinkType.Tsu)) { linkPos += CalcUnitLength(u); } // 促音終端キーを追加 area.SetWeight(linkPos, area.Points.Last().Value); maxEndAdded = true; // 長音部分をカット units = units.SkipWhile(u => u.LinkType == LinkType.Tsu); // 次のユニットを取得 unit = units.FirstOrDefault(); } // 最大開口終端キーが追加されていなければ追加 if (!maxEndAdded) { area.SetWeight(linkPos, area.Points.Last().Value); } // 継続部分を計算 // 継続より後ろの長音や促音も継続扱い if (unit != null) { // 次の音開始位置をシフト foreach (var u in units) { linkPos += CalcUnitLength(u); } } // 終端キーを追加 var endPos = linkPos + openCloseLen; if (nextUnit != null && nextUnit.LinkType != LinkType.Normal) { // 一旦口を閉じるならば次の音の開口時間の1/2だけ終端を早める var closeHalfPos = endPos - CalcOpenCloseLength(nextUnit) / 2; closeHalfPos = Math.Max(closeHalfPos, linkPos); area.SetWeight(closeHalfPos, 0); } else if ( nextUnit != null && nextUnit.LipId == id && nextUnit.LinkType == LinkType.Normal) { // 後続と同じ口形状の場合 // 次の音の開口時間の1/2だけ終端を早める var closeHalfPos = endPos - CalcOpenCloseLength(nextUnit) / 2; closeHalfPos = Math.Max(closeHalfPos, linkPos); // 接続ウェイト値を終端値とする // 最大開口終端キーのウェイト値に比例させる(長音対応) var weight = DecideSameLipLinkWeight(areaUnits[0], nextUnit); weight *= area.Points.Last().Value; area.SetWeight(closeHalfPos, weight); } else { area.SetWeight(endPos, 0); } // タイムラインに追加 tlSet[id].KeyAreas.Add(area); // 次の音の開始位置を返す return linkPos; }
/// <summary> /// ユニットの最大開口の長さを算出する。 /// </summary> /// <param name="unit">ユニット。</param> /// <returns>最大開口の長さ。</returns> private long CalcMaxOpenLength(LipSyncUnit unit) { return (CalcUnitLength(unit) - CalcOpenCloseLength(unit)); }
/// <summary> /// ユニットの開口開始位置から最大開口終了位置までの長さを算出する。 /// </summary> /// <param name="unit">ユニット。</param> /// <returns>開口開始位置から最大開口終了位置までの長さ。</returns> private long CalcUnitLength(LipSyncUnit unit) { return ( LipTimelineSet.LengthPerUnit * unit.LengthPercent / 100); }
/// <summary> /// 開口および閉口の長さを算出する。 /// </summary> /// <param name="unit">ユニット。</param> /// <returns>開口および閉口の長さ。</returns> private long CalcOpenCloseLength(LipSyncUnit unit) { return ( LipTimelineSet.LengthPerUnit * unit.LengthPercent * this.LinkLengthPercent / 10000L); }
/// <summary> /// ユニットリストをキー領域に変換してタイムラインに追加する。 /// </summary> /// <param name="tlSet">追加先のタイムラインセット。</param> /// <param name="beginPlace">開始キー位置。</param> /// <param name="areaUnits">ユニットリスト。</param> /// <param name="prevUnit">先行発声ユニット。無いならば null 。</param> /// <param name="nextUnit">後続発声ユニット。無いならば null 。</param> /// <returns>後続のキー領域を開始すべきキー位置。</returns> private long AddAreaUnitList( LipTimelineSet tlSet, long beginPlace, IList<LipSyncUnit> areaUnits, LipSyncUnit prevUnit, LipSyncUnit nextUnit) { if (areaUnits.Count <= 0) { return beginPlace; } // 最初のユニットと口形状種別IDを取得 var unit = areaUnits[0]; var id = unit.LipId; var area = new KeyArea(); // 開口および閉口の長さを算出 long openCloseLen = CalcOpenCloseLength(unit); // 開始キーを追加 long openHalfPos = beginPlace + openCloseLen / 2; if (prevUnit != null && unit.LinkType != LinkType.Normal) { // 一旦口を(半分)閉じるならば開口時間の1/2まで経過してから開口開始 area.AddPointAfter(openHalfPos, 0); } else if (prevUnit != null && prevUnit.LipId == id) { // 直前と同じ口形状ならば接続ウェイト値の1/2を設定 var lw = DecideSameLipLinkWeight(prevUnit, unit); area.AddPointAfter(openHalfPos, lw / 2); } else { area.AddPointAfter(beginPlace, 0); } // 最大開口開始キーを追加 long maxBeginPos = beginPlace + openCloseLen; maxBeginPos = area.AddPointAfter(maxBeginPos, 1); // 次の音開始位置(=最大開口終了位置)を算出 long linkPos = maxBeginPos + CalcMaxOpenLength(unit); // 最初のユニットをカット var units = areaUnits.Skip(1); // 次のユニットを取得 unit = units.FirstOrDefault(); // 最大開口終端位置追加済みフラグ bool maxEndAdded = false; // 長音部分を計算 if (unit != null && unit.LinkType == LinkType.LongSound) { // 次の音開始位置をシフト foreach ( var u in units.TakeWhile(u => u.LinkType == LinkType.LongSound)) { linkPos += CalcUnitLength(u); } // 長音終端キーを追加 linkPos = area.AddPointAfter(linkPos, this.LongSoundLastWeight); maxEndAdded = true; // 長音部分をカット units = units.SkipWhile(u => u.LinkType == LinkType.LongSound); // 次のユニットを取得 unit = units.FirstOrDefault(); } // 促音部分を計算 if (unit != null && unit.LinkType == LinkType.Tsu) { // 次の音開始位置をシフト foreach ( var u in units.TakeWhile(u => u.LinkType == LinkType.Tsu)) { linkPos += CalcUnitLength(u); } // 促音終端キーを追加 linkPos = area.AddPointAfter(linkPos, area.Points.Last().Value); maxEndAdded = true; // 長音部分をカット units = units.SkipWhile(u => u.LinkType == LinkType.Tsu); // 次のユニットを取得 unit = units.FirstOrDefault(); } // 最大開口終端キーが追加されていなければ追加 if (!maxEndAdded) { linkPos = area.AddPointAfter(linkPos, area.Points.Last().Value); } // 継続部分を計算 // 継続より後ろの長音や促音も継続扱い if (unit != null) { // 次の音開始位置をシフト foreach (var u in units) { linkPos += CalcUnitLength(u); } } // 終端キーを追加 long endPos = linkPos + openCloseLen; if (nextUnit != null && nextUnit.LinkType == LinkType.PreClose) { // 一旦口を閉じるならば次の音の開口時間の1/2だけ終端を早める long closeHalfPos = endPos - CalcOpenCloseLength(nextUnit) / 2; closeHalfPos = Math.Max(closeHalfPos, linkPos); area.AddPointAfter(closeHalfPos, 0); } else if ( nextUnit != null && nextUnit.LipId == id && nextUnit.LinkType == LinkType.Normal) { // 後続と同じ口形状ならば接続ウェイト値の1/2を設定 long closeHalfPos = endPos - CalcOpenCloseLength(nextUnit) / 2; closeHalfPos = Math.Max(closeHalfPos, linkPos); var lw = DecideSameLipLinkWeight(unit, nextUnit); area.AddPointAfter(closeHalfPos, lw / 2); } else { area.AddPointAfter(endPos, 0); } // タイムラインに追加 tlSet[id].KeyAreas.Add(area); // 次の音の開始位置を返す return linkPos; }