/// <summary> /// 口形状キー領域をモーフウェイト値で変換してタイムラインへ追加する。 /// </summary> /// <param name="table">追加先のタイムラインテーブル。</param> /// <param name="lipKeyArea">口形状キー領域。</param> /// <param name="morphWeight">モーフ名とそのウェイト値。</param> /// <param name="morphEtoAI"> /// "え" から "あ","い" へのモーフ変更を行うならば true 。 /// </param> private void AddMorphKeyArea( MorphTimelineTable table, TimelineKeyArea lipKeyArea, MorphWeightData morphWeight, bool morphEtoAI) { // "え" から "あ","い" への変換を行うか? bool e2ai = (morphEtoAI && morphWeight.MorphName == "え"); // キー領域作成 var area = lipKeyArea.Clone(); foreach (var p in lipKeyArea.Points.Keys) { area.Points[p] *= morphWeight.Weight * (e2ai ? 0.5f : 1.0f); } // タイムラインに追加 // モーフ名が空文字列のものは弾く if (e2ai) { table.GetOrAddNew("あ").KeyAreas.Add(area); table.GetOrAddNew("い").KeyAreas.Add(area); } else if (!string.IsNullOrEmpty(morphWeight.MorphName)) { table.GetOrAddNew(morphWeight.MorphName).KeyAreas.Add(area); } }
/// <summary> /// 閉口タイムラインを修正する。 /// </summary> /// <param name="tlSet">修正対象のタイムラインセット。</param> private void FixClosedTimeline(TimelineSet tlSet) { // 閉口抜きのタイムライン配列作成 var tlAiueo = ( from it in tlSet where it.Key != LipId.Closed select it.Value) .ToArray(); // 全キー位置と閉口以外のウェイト値合計のテーブル作成 var points = new SortedList<decimal, float>( tlSet.GetAllPlaces().ToDictionary( p => p, p => tlAiueo.Sum(tl => tl.GetWeight(p)))); // 閉口タイムラインをクリア tlSet.Closed.KeyAreas.Clear(); // 閉口タイムラインにキー領域を追加するデリゲート作成 Action<TimelineKeyArea> areaAdder = a => { // キーが足りない or ウェイト値がすべて 0 なら追加しない if (a.Points.Count < 2 || a.Points.All(pw => pw.Value <= 0)) { return; } // エッジを閉口状態としないなら先頭と終端のウェイト値を 0 にする if (!this.IsEdgeClosed) { a.Points[a.BeginPlace] = 0; a.Points[a.EndPlace] = 0; } // 追加 tlSet.Closed.KeyAreas.Add(a); }; var area = new TimelineKeyArea(); // 閉口タイムラインを作成 foreach (var pw in points) { // (1 - 閉口以外のウェイト値合計) を閉口ウェイト値とする var weight = Math.Max(0, 1 - pw.Value); if (weight > 0) { // キー追加 area.SetWeight(pw.Key, weight); } else { // 終端キー追加 area.SetWeight(pw.Key, 0); // 閉口タイムラインにキー領域を追加 areaAdder(area); // 新しいキー領域を作成開始 area = new TimelineKeyArea(); area.SetWeight(pw.Key, 0); } } // 閉口タイムラインに最後のキー領域を追加 areaAdder(area); }
/// <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; }