/* * TimeTableGroup.cs * Copyright (c) 2007-2009 kbinani * * This file is part of LipSync. * * LipSync is free software; you can redistribute it and/or * modify it under the terms of the BSD License. * * LipSync is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Runtime.Serialization; using System.Windows.Forms; using CurveEditor; namespace LipSync { /// /// TimeTableをグループ化して取り扱うためのクラス /// [Serializable] public class TimeTableGroup : IDisposable, ICloneable, IDrawObject { [Obsolete] private Character m_character; internal List list; private string m_Text; private int m_intValue; [Obsolete] private Point position; private int m_zOrder; [Obsolete] private float scale; [OptionalField] private bool folded; [OptionalField] public BezierChain mc_x; [OptionalField] public BezierChain mc_y; [OptionalField] public BezierChain mc_scale; [OptionalField] public BezierChain mc_alpha; [OptionalField] public BezierChain mc_rotate; [OptionalField] private Character3 m_character3; [OptionalField] private string m_character_path; [OptionalField] private bool m_position_fixed = false; public Size ImageSize { get { if ( m_character3 != null ) { return m_character3.Size; } else { return new Size(); } } } public bool IsXFixedAt( float time ) { float min, max; if ( mc_x.GetKeyMinMax( out min, out max ) ) { if ( min <= time && time <= max ) { return true; } } return false; } public bool IsYFixedAt( float time ) { float min, max; if ( mc_y.GetKeyMinMax( out min, out max ) ) { if ( min <= time && time <= max ) { return true; } } return false; } /// /// 描画オブジェクトの位置が固定されるかどうかを示す値を取得,または設定します /// public bool PositionFixed { get { return m_position_fixed; } set { m_position_fixed = value; } } public static void GenerateLipSyncFromVsq( TimeTable a_time_table, ref TimeTableGroup temp, Character3 character, float total_sec, bool close_mouth_when_same_vowel_repeated, float frame_rate, float combine_threshold ) { TimeTable time_table = (TimeTable)a_time_table.Clone(); for ( int i = 0; i < time_table.Count - 1; i++ ) { if ( time_table[i + 1].begin - time_table[i].end <= combine_threshold ) { time_table[i].end = time_table[i + 1].begin; } } const float CLOSE_LENGTH = 2.99f; const float CLOSE_LENGTH_SHORT = 1.99f; if ( time_table.Type != TimeTableType.vsq ) { return; } // 口パクの母音トラックを追加 int base_image = -1; for ( int k = 0; k < character.Count; k++ ) { switch ( character[k].title ) { case "a": case "aa": case "i": case "u": case "e": case "o": case "xo": case "nn": temp.Add( new TimeTable( character[k].title, 0, TimeTableType.character, null ) ); break; case "base": temp.Add( new TimeTable( character[k].title, 0, TimeTableType.character, null ) ); base_image = temp.Count - 1; break; default: temp.Add( new TimeTable( character[k].title, 0, TimeTableType.character, null ) ); break; } } float begin; float end; float last_end = -1f; string body; string lyric;//歌詞 string phonetics;//発音記号 VowelType last_vowel = VowelType.def; for ( int entry = 0; entry < time_table.Count; entry++ ) { #region エントリを追加 begin = time_table[entry].begin; end = time_table[entry].end; body = time_table[entry].body; string[] spl = body.Split( new char[] { '(' } ); phonetics = spl[1].Replace( ")", "" ); lyric = spl[0].Replace( " ", "" ); VowelType type = VowelType.Attach( lyric ); if ( entry > 0 && lyric == "ー" ) { type = last_vowel; } if ( phonetics == "br1" || phonetics == "br2" || phonetics == "br3" || phonetics == "br4" || phonetics == "br5" ) { type = VowelType.aa; } string separate = ""; if ( type.Equals( VowelType.def ) ) { // VowelType.Attachでは判定できなかったとき。 MouthSet mouth_set = VowelType.AttachEx( phonetics ); type = mouth_set.main; separate = mouth_set.prep.ToString(); if ( separate == "def" ) { separate = ""; } } else { switch ( lyric ) { #region NN //b case "ば": case "ぶ": case "べ": case "ぼ": if ( VowelType.IsRegisteredToNN( "b" ) ) { separate = "nn"; } break; //p case "ぱ": case "ぷ": case "ぺ": case "ぽ": if ( VowelType.IsRegisteredToNN( "p" ) ) { separate = "nn"; } break; //m case "ま": case "む": case "め": case "も": if ( VowelType.IsRegisteredToNN( "m" ) ) { separate = "nn"; } break; //b' case "びゃ": case "び": case "びゅ": case "びぇ": case "びょ": if ( VowelType.IsRegisteredToNN( "b'" ) ) { separate = "nn"; } break; //p' case "ぴゃ": case "ぴ": case "ぴゅ": case "ぴぇ": case "ぴょ": if ( VowelType.IsRegisteredToNN( "p'" ) ) { separate = "nn"; } break; //m' case "みゃ": case "み": case "みゅ": case "みぇ": case "みょ": if ( VowelType.IsRegisteredToNN( "m'" ) ) { separate = "nn"; } break; #endregion #region U //p\ case "ふぁ": //case "ふ": case "ふぇ": case "ふぉ": if ( VowelType.IsRegisteredToU( @"p\" ) ) { separate = "u"; } break; //p\' case "ふぃ": case "ふゅ": if ( VowelType.IsRegisteredToU( @"p\'" ) ) { separate = "u"; } break; //w case "わ": case "うぃ": case "うぇ": //case "を": case "うぉ": if ( VowelType.IsRegisteredToU( "w" ) ) { separate = "u"; } break; //ts case "つぁ": case "つぃ": //case "つ": case "つぇ": case "つぉ": if ( VowelType.IsRegisteredToU( "ts" ) ) { separate = "u"; } break; //dz case "づぁ": case "づぃ": //case "づ": case "づぇ": case "づぉ": if ( VowelType.IsRegisteredToU( "dz" ) ) { separate = "u"; } break; #endregion #region I //k' case "きゃ": //case "き": case "きゅ": case "きょ": if ( VowelType.IsRegisteredToI( "k'" ) ) { separate = "i"; } break; //g' case "ぎゃ": //case "ぎ": case "ぎゅ": case "ぎょ": if ( VowelType.IsRegisteredToI( "g'" ) ) { separate = "i"; } break; //S case "しゃ": //case "し": case "しゅ": case "しぇ": case "しょ": if ( VowelType.IsRegisteredToI( "S" ) ) { separate = "i"; } break; //dZ //case "ぢ": case "ぢゅ": case "ぢぇ": case "ぢょ": if ( VowelType.IsRegisteredToI( "dZ" ) ) { separate = "i"; } break; //tS case "ちゃ": //case "ち": case "ちゅ": case "ちぇ": case "ちょ": if ( VowelType.IsRegisteredToI( "tS" ) ) { separate = "i"; } break; //J case "にゃ": //case "に": case "にゅ": case "にぇ": case "にょ": if ( VowelType.IsRegisteredToI( "J" ) ) { separate = "i"; } break; //C case "ひゃ": //case "ひ": case "ひゅ": case "ひぇ": case "ひょ": if ( VowelType.IsRegisteredToI( "C" ) ) { separate = "i"; } break; // another case "いぇ": separate = "i"; break; #endregion } } bool mode_sp = false; if ( close_mouth_when_same_vowel_repeated ) { // 母音が連続する場合を検出 if ( separate == "" && begin == last_end && last_vowel.Equals( type ) && lyric != "ー" ) { if ( type.Equals( VowelType.a ) ) { if ( lyric != "あ" && !phonetics.StartsWith( "a" ) ) { separate = "nn"; mode_sp = true; } } else if ( type.Equals( VowelType.i ) ) { if ( lyric != "い" && !phonetics.StartsWith( "i" ) ) { separate = "nn"; mode_sp = true; } } else if ( type.Equals( VowelType.u ) ) { if ( lyric != "う" && !phonetics.StartsWith( "u" ) ) { separate = "nn"; mode_sp = true; } } else if ( type.Equals( VowelType.e ) ) { if ( lyric != "え" && !phonetics.StartsWith( "e" ) ) { separate = "nn"; mode_sp = true; } } else if ( type.Equals( VowelType.o ) ) { if ( lyric != "お" && !phonetics.StartsWith( "o" ) ) { separate = "nn"; mode_sp = true; } } } } last_end = end; last_vowel = type; if ( separate != "" ) { float total = end - begin; float tmp_begin = begin; float tmp_end; float close_length; if ( mode_sp ) { close_length = CLOSE_LENGTH_SHORT; } else { close_length = CLOSE_LENGTH; } if ( total > 2f * close_length / frame_rate ) { //エントリの長さが、十分長い場合 tmp_end = begin + close_length / frame_rate; } else { tmp_end = begin + total / 2f; } begin = tmp_end; //for ( int base_track = 0; base_track < s.m_groups_character[mouth_group].Count; base_track++ ) { for ( int base_track = 0; base_track < temp.Count; base_track++ ) { if ( temp[base_track].Text == separate ) { //temp[base_track].Add( new TimeTableEntry( tmp_begin, tmp_end, separate ) ); temp.Interrup( base_track, tmp_begin, tmp_end ); break; } } } for ( int base_track = 0; base_track < temp.Count; base_track++ ) { if ( temp[base_track].Text == type.ToString() ) { //temp[base_track].Add( new TimeTableEntry( begin, end, type.ToString() ) ); temp.Interrup( base_track, begin, end ); break; } } #endregion } // 連続しているやつがあればマージする bool change = true; while ( change ) { change = false; for ( int track = 0; track < temp.Count; track++ ) { for ( int entry = 0; entry < temp[track].Count - 1; entry++ ) { if ( temp[track][entry].body == temp[track][entry + 1].body && temp[track][entry].end == temp[track][entry + 1].begin ) { temp[track][entry].end = temp[track][entry + 1].end; temp[track].RemoveAt( entry + 1 ); change = true; break; } } } } // is_defaultな画像用のエントリを追加 for ( int k = 0; k < character.Count; k++ ) { if ( character[k].IsDefault ) { temp.Fill( k, total_sec ); } } } /// /// 画面上に表示されるトラックの数 /// [Browsable(false)] public int VisibleTracks { get { if ( folded ) { return 0; } else { return list.Count; } } } /// /// 第track番目のトラックの、最初のエントリのON時刻を返します。エントリが未だ無い場合、負の値を返します /// /// /// public float GetFirstOn( int track ) { if ( list[track].Count > 0 ) { return list[track][0].begin; } else { return -1f; } } public float GetFirstOn() { if ( list.Count > 0 ) { float[] first = new float[list.Count]; for ( int i = 0; i < list.Count; i++ ) { if ( list[i].Count > 0 ) { first[i] = list[i][0].begin; } else { first[i] = -1f; } } float res = float.MaxValue; foreach ( float val in first ) { if ( val >= 0f ) { res = Math.Min( res, val ); } } if ( res == float.MaxValue ) { return -1f; } else { return res; } } else { return -1f; } } [Browsable(false)] public Character3 Character { get { return m_character3; } set { m_character3 = value; } } /// /// 指定した時刻における、このオブジェクトの位置を取得します /// /// /// public PointF GetPosition( float time ) { return new PointF( mc_x.GetValue( time ), mc_y.GetValue( time ) ); } /// /// 指定した時刻における、このオブジェクトのスケールを取得します /// /// /// public float GetScale( float time ) { return mc_scale.GetValue( time ); } /// /// 指定した時刻における、このオブジェクトのアルファ値を取得します /// /// /// public float GetAlpha( float time ) { float a = mc_alpha.GetValue( time ); if( a > 1f ) { a = 1f; } else if( a < 0f ) { a = 0f; } return a; } /// /// 指定した時刻における、このオブジェクトの回転角度を取得します /// /// /// public float GetRotate( float time ) { float r = mc_rotate.GetValue( time ); if( r > 360f ) { r = r % 360f; } else if( r < 0f ) { r = r % 360f + 360f; } return r; } [OnDeserializing] private void onDeserializing( StreamingContext sc ) { folded = false; } [OnDeserialized] private void onDeserialized( StreamingContext sc ) { #if DEBUG Console.WriteLine( "TimeTableGroup.onDeserialized(StreamingContext)" ); #endif if( mc_x == null ) { mc_x = new BezierChain( Common.CURVE_X ); mc_x.Default = position.X; } if( mc_y == null ) { mc_y = new BezierChain( Common.CURVE_Y ); mc_y.Default = position.Y; } if( mc_scale == null ) { mc_scale = new BezierChain( Common.CURVE_SCALE ); mc_scale.Default = scale; } if( mc_alpha == null ) { mc_alpha = new BezierChain( Common.CURVE_ALPHA ); mc_alpha.Default = 1.0f; } if( mc_rotate == null ) { mc_rotate = new BezierChain( Common.CURVE_ROTATE ); } mc_x.Color = Common.CURVE_X; mc_y.Color = Common.CURVE_Y; mc_scale.Color = Common.CURVE_SCALE; mc_alpha.Color = Common.CURVE_ALPHA; #if DEBUG Common.DebugWriteLine( "TimeTableGroup.onDeserialized; (m_character==null);" + (m_character == null) ); #endif if( m_character != null ) { m_character3 = new Character3( m_character ); m_character.Dispose(); m_character = null; } if ( list != null ) { if ( list.Count > 0 ) { if ( list[0].Type == TimeTableType.character && m_character3 != null ) { List buf = new List(); for ( int i = 0; i < list.Count; i++ ) { buf.Add( (TimeTable)list[i].Clone() ); } list.Clear(); for ( int i = 0; i < m_character3.Count; i++ ) { for ( int j = 0; j < buf.Count; j++ ) { if ( buf[j].Text == m_character3[i].title ) { list.Add( buf[j] ); } } } } } } } public void Dispose() { if( m_character != null ) { m_character.Dispose(); } if( list != null ) { list.Clear(); } } public void Fill( int target, float total_sec ) { string title = list[target].Text; string tag = m_character3[title].tag; //同じタグを持つタイムテーブルのインデクスを列挙 List same_tag = new List(); for( int i = 0; i < list.Count; i++ ) { if( i != target ) { string t_title = list[i].Text; string t_tag = m_character3[t_title].tag; if( t_tag == tag ) { same_tag.Add( i ); } } } list[target].Clear(); if( same_tag.Count == 0 || tag == "" ) { list[target].Add( new TimeTableEntry( 0f, total_sec, title ) ); return; } //まず開始時間の中から一番速いエントリを探す float min = float.MaxValue; int track_of_min = -1; foreach( int index in same_tag ) { if( list[index].Count > 0 ) { min = Math.Min( min, list[index][0].begin ); track_of_min = index; } } if( track_of_min < 0 ) { // 排他のかかるタグはあるが、そのうちのいづれにもまだエントリが含まれていない場合 list[target].Add( new TimeTableEntry( 0f, total_sec, title ) ); return; } #if DEBUG MessageBox.Show( "min=" + min ); #endif if( min != 0f ) { list[target].Add( new TimeTableEntry( 0f, min, title ) ); list[target].Sort(); } bool added = true; while( added ) { added = false; // 終了時刻がminより後で、かつ開始時刻がminと同じか速いが無いかどうか検索 bool found = true; while( found ) { found = false; foreach( int index in same_tag ) { for( int entry = 0; entry < list[index].Count; entry++ ) { if( list[index][entry].end > min && list[index][entry].begin <= min ) { found = true; //track_of_min = index; //entry_of_min = entry; min = list[index][entry].end; break; } } if( found ) { break; } } } // 新しいminを検索 float begin = min; // 開始時刻がbeginより後のエントリのうち一番早いものを検索 min = float.MaxValue; found = false; foreach( int index in same_tag ) { for( int entry = 0; entry < list[index].Count; entry++ ) { if( begin < list[index][entry].begin ) { min = Math.Min( min, list[index][entry].begin ); //track_of_min = index; //entry_of_min = entry; found = true; break; } } } float end; if( found ) { end = min; added = true; } else { end = total_sec; } list[target].Add( new TimeTableEntry( begin, end, title ) ); #if DEBUG Common.DebugWriteLine( "" + (end - begin) ); #endif list[target].Sort(); } } /// /// 指定したTimeTableGroupの、第track番目のTimeTableに、エントリitemを割り込ませます /// 排他制御がかかる場合は、挿入されたitem以外の部分が削られます /// Undo, redo用の処理は、この関数の呼び出し元で行われる /// /// /// /// public void Interrup( int target, float t1, float t2 ) { InterrupCore( target, t1, t2, "", false ); } public void Interrup( int target, float t1, float t2, string str ) { InterrupCore( target, t1, t2, str, true ); } private void InterrupCore( int target, float t1, float t2, string str, bool body_specified ) { if( target < 0 ) { return; } float begin, end; if( t1 > t2 ) { begin = t2; end = t1; } else if( t2 > t1 ) { begin = t1; end = t2; } else { return; } string title; if( body_specified ) { title = str; } else { title = list[target].Text; } using( TimeTableEntry item = new TimeTableEntry( begin, end, title ) ) { // タグが設定されているか? string tag;// = ""; if( m_character3 != null ) { tag = m_character3[title].tag; } else { tag = ""; } trimToInterrup( target, item ); // タグ設定がある場合。排他のかかるほかのエントリも調節 if( tag != "" ) { for( int track = 0; track < list.Count; track++ ) { if( track != target ) { string this_tag = m_character3[list[track].Text].tag; if( this_tag == tag ) { trimToInterrup( track, item ); } } } } list[target].Add( (TimeTableEntry)item.Clone() ); list[target].Sort(); } } private void trimToInterrup( int track, TimeTableEntry item ) { // 割り込み対象のタイムテーブルでかぶっているエントリを調節 bool changed = true; while( changed ) { changed = false; for( int entry = 0; entry < list[track].Count; entry++ ) { float list_begin = list[track][entry].begin; float list_end = list[track][entry].end; if( item.begin <= list_begin && list_begin < item.end && item.end < list_end ) { // list : [***********] // item : [*****] // ↓↓結果 // list : [*****][*******] list[track][entry].begin = item.end; changed = true; break; } else if( item.begin <= list_begin && list_end <= item.end/*item.begin < list_begin && list_begin <= item.end && list_end <= item.end*/ ) { // list : [***********] // item : [*****************] // ↓↓結果 // list : [*****************] list[track].RemoveAt( entry ); changed = true; break; } else if( list_begin < item.begin && item.begin < list_end && item.end < list_end ) { // list : [***********] // item : [*****] // ↓↓結果 // list : [*][*****][*] float old_end = list[track][entry].end; list[track][entry].end = item.begin; list[track].Add( new TimeTableEntry( item.end, old_end, list[track][entry].body ) ); list[track].Sort(); changed = true; break; } else if( list_begin < item.begin && item.begin < list_end && list_end < item.end ) { // list : [***********] // item : [***********] // ↓↓結果 // list : [*][***********] list[track][entry].end = item.begin; changed = true; break; } else if( Math.Abs( list_begin - item.begin ) <= float.Epsilon && Math.Abs( list_end - item.end ) <= float.Epsilon ) { // list : [***********] // item : [***********] // ↓↓結果 // list : [***********] list[track].RemoveAt( entry ); changed = true; break; } } } } /// /// このタイムテーブルグループの、時刻timeの時点でONとなっているエントリ名 /// のリストを、\n区切りの文字列で返します /// /// /// public string[] GetDrawObjectNames( float time ) { List draw = new List(); for( int track = 0; track < list.Count; track++ ) { int start_entry = list[track].Value; for( int entry = start_entry; entry < list[track].Count; entry++ ) { if( list[track][entry].begin <= time && time <= list[track][entry].end ) { draw.Add( list[track].Text ); } } } return draw.ToArray(); } public int[] GetDrawObjectIndex( float time, string mouth ) { string exclude_tag = ""; if ( mouth.Length > 0 && m_character3 != null ) { int index_of_mouth = m_character3[mouth].Z; if ( index_of_mouth >= 0 ) { exclude_tag = m_character3[index_of_mouth].tag; } } List draw = new List(); for( int track = 0; track < list.Count; track++ ) { if ( exclude_tag.Length > 0 && m_character3 != null ) { if ( m_character3[list[track].Text].tag == exclude_tag ) { if ( m_character3[list[track].Text].title == mouth ) { draw.Add( track ); } continue; } } int start_entry = list[track].Value; for( int entry = start_entry; entry < list[track].Count; entry++ ) { if( list[track][entry].begin <= time && time < list[track][entry].end ) { draw.Add( track ); break; } } } return draw.ToArray(); } /// /// このタイムテーブルグループが、画面上で折りたたみ表示されているかどうかを表す値を取得または設定します /// [Browsable( false )] public bool Folded { get { return folded; } set { folded = value; } } /// /// このタイムテーブルグループのデフォルトの表示スケールを取得または設定します /// public float Scale { get { return mc_scale.Default; } set { mc_scale.Default = value; } } public void Insert( int index, TimeTable table ) { list.Insert( index, table ); } public object Clone() { TimeTableGroup tmp = new TimeTableGroup( m_Text, m_intValue, m_character3 ); for( int i = 0; i < list.Count; i++ ) { tmp.list.Add( (TimeTable)list[i].Clone() ); } //tmp.position = position; tmp.m_zOrder = m_zOrder; //tmp.scale = scale; tmp.folded = folded; tmp.mc_alpha = (BezierChain)this.mc_alpha.Clone(); tmp.mc_rotate = (BezierChain)this.mc_rotate.Clone(); tmp.mc_scale = (BezierChain)this.mc_scale.Clone(); tmp.mc_x = (BezierChain)this.mc_x.Clone(); tmp.mc_y = (BezierChain)this.mc_y.Clone(); tmp.m_position_fixed = this.m_position_fixed; return tmp; } /// /// このタイムテーブルグループのZオーダーを取得または設定します /// [Browsable( false )] public int ZOrder { get { return m_zOrder; } set { m_zOrder = value; } } public void RemoveAt( int track ) { list.RemoveAt( track ); } /// /// このタイムテーブルグループの表示位置を取得または設定します /// [Browsable( false )] public Point Position { get { return new Point( (int)mc_x.Default, (int)mc_y.Default ); } set { PointF t = value; mc_x.Default = t.X; mc_y.Default = t.Y; } } /// /// このタイムテーブルグループの表示位置を取得または設定します。 /// このプロパティはプロパティビューでの表示用です。 /// public Position Location { get { return new Position( mc_x.Default, mc_y.Default ); } set { mc_x.Default = value.X; mc_y.Default = value.Y; } } [Browsable( false )] public int Value { get { return m_intValue; } set { m_intValue = value; } } public string Text { get { return m_Text; } set { m_Text = value; } } public void Clear() { for( int i = 0; i < list.Count; i++ ) { list[i].Clear(); } list.Clear(); } [Browsable( false )] public TimeTable this[int track] { get { return list[track]; } set { list[track] = (TimeTable)value; } } public TimeTableGroup( string text, int value, Character3 character/*, Image baseImage*/ ) { list = new List(); m_Text = text; /*m_baseImage = baseImage;*/ if( character != null ) { m_character3 = (Character3)character.Clone(); } else { m_character3 = null; } m_intValue = value; //position = new Point( 0, 0 ); m_zOrder = 0; scale = 1.0f; mc_x = new BezierChain( Common.CURVE_X ); mc_y = new BezierChain( Common.CURVE_Y ); mc_scale = new BezierChain( Common.CURVE_SCALE ); mc_scale.Default = 1f; mc_alpha = new BezierChain( Common.CURVE_ALPHA ); mc_alpha.Default = 1f; mc_rotate = new BezierChain( Common.CURVE_ROTATE ); folded = false; } public void Add( TimeTable new_time_table ) { list.Add( (TimeTable)new_time_table.Clone() ); } [Browsable( false )] public int Count { get { if( list != null ) { return list.Count; } else { return 0; } } } } }