/// <summary> /// インターフェースIPaletteToolのメンバー /// </summary> /// <param name="track">編集対象のトラック</param> /// <param name="manager">Cadenciiのマネージャ</param> /// <param name="ids">クリックされたイベントのInternalIDが格納された配列</param> /// <param name="button">クリックされたときのマウスボタン</param> /// <returns></returns> public bool edit(cadencii.vsq.VsqTrack track, int[] ids, System.Windows.Forms.MouseButtons button) { // コントロールカーブの時間方向の解像度を,Cadenciiの設定値から取得 int resol = AppManager.editorConfig.getControlCurveResolutionValue(); for (int i = 0; i < ids.Length; i++) { int internal_id = ids[i]; foreach (var item in track.getNoteEventIterator()) { // 指定されたInternalIDと同じなら,編集する if (item.InternalID == internal_id) { // Brightnessカーブを取得 cadencii.vsq.VsqBPList bri = track.getCurve("BRI"); // 音符の最後の位置でのBRIを取得.処理の最後で追加 int value_at_end = bri.getValue(item.Clock + item.ID.Length); // これから編集しようとしている範囲にすでに値がある場合,邪魔なので削除する bri.keyClockIterator() .Where((clock) => item.Clock <= clock && clock <= item.Clock + item.ID.Length) .ToList() .ForEach((clock) => bri.remove(clock)); // 直前に指定したBRI値.最初はありえない値にしておく int last_v = -1; // 時間方向解像度(resol)ごとのクロックに対して,順次BRIを設定 for (int clock = item.Clock; clock <= item.Clock + item.ID.Length; clock += resol) { // BRIを取得.x=0が音符の先頭,x=1が音符の末尾.getCurve関数は,この仕様を満たすようにBRIを返すように,お好みで定義 float x = (clock - item.Clock) / (float)item.ID.Length; int v = getCurve(x); if (last_v != v) { // 直前に指定した値と違うときだけ追加. bri.add(clock, v); } // 「直前の値」を更新 last_v = v; } // 音符末尾の位置のBRIを強制的に元の値に戻す.これをやらないと, // その音符の末尾以降のBRIがそのまま編集の影響を受けてしまう bri.add(item.Clock + item.ID.Length, value_at_end); break; } } } return(true); }
/// <summary> /// vsqの指定したトラックを元に,トラックを1つだけ持つustを構築します /// </summary> /// <param name="vsq"></param> /// <param name="track_index"></param> /// <param name="id_map">UstEventのIndexフィールドと、元になったVsqEventのInternalIDを対応付けるマップ。キーがIndex、値がInternalIDを表す</param> public UstFile(VsqFile vsq, int track_index, SortedDictionary <int, int> id_map) { VsqFile work = (VsqFile)vsq.clone(); //work.removePart( 0, work.getPreMeasureClocks() ); VsqTrack vsq_track = work.Track[track_index]; // デフォルトのテンポ if (work.TempoTable.Count <= 0) { m_tempo = 120.0f; } else { m_tempo = (float)(60e6 / (double)work.TempoTable[0].Tempo); } m_tempo_table = new List <TempoTableEntry>(); m_tempo_table.Clear(); // ustには、テンポチェンジを音符の先頭にしか入れられない // あとで音符に反映させるためのテンプレートを作っておく TempoVector tempo = new TempoVector(); int last_clock = 0; int itempo = (int)(60e6 / m_tempo); foreach (var item in vsq_track.getNoteEventIterator()) { if (last_clock < item.Clock) { // 休符Rの分 tempo.Add(new TempoTableEntry(last_clock, itempo, work.getSecFromClock(last_clock))); } tempo.Add(new TempoTableEntry(item.Clock, itempo, work.getSecFromClock(item.Clock))); last_clock = item.Clock + item.ID.getLength(); } if (tempo.Count == 0) { tempo.Add(new TempoTableEntry(0, (int)(60e6 / m_tempo), 0.0)); } // tempoの中の各要素の時刻が、vsq.TempoTableから計算した時刻と合致するよう調節 #if DEBUG sout.println("UstFile#.ctor; before; list="); for (int i = 0; i < tempo.Count; i++) { TempoTableEntry item = tempo[i]; sout.println(" #" + i + "; c" + item.Clock + "; T" + item.Tempo + "; t" + (60e6 / item.Tempo) + "; sec" + item.Time); } #endif TempoTableEntry prev = tempo[0]; for (int i = 1; i < tempo.Count; i++) { TempoTableEntry item = tempo[i]; double sec = item.Time - prev.Time; int delta = item.Clock - prev.Clock; // deltaクロックでsecを表現するにはテンポをいくらにすればいいか? int draft = (int)(480.0 * sec * 1e6 / (double)delta); // 丸め誤差が入るので、Timeを更新 // ustに実際に記録されるテンポはいくらか? float act_tempo = (float)double.Parse(PortUtil.formatDecimal("0.00", 60e6 / draft)); int i_act_tempo = (int)(60e6 / act_tempo); prev.Tempo = i_act_tempo; item.Time = prev.Time + 1e-6 * delta * prev.Tempo / 480.0; prev = item; } #if DEBUG sout.println("UstFile#.ctor; after; list="); for (int i = 0; i < tempo.Count; i++) { TempoTableEntry item = tempo[i]; sout.println(" #" + i + "; c" + item.Clock + "; T" + item.Tempo + "; t" + (60e6 / item.Tempo) + "; sec" + item.Time); } sout.println("UstFile#.ctor; vsq.TempoTable="); for (int i = 0; i < work.TempoTable.Count; i++) { TempoTableEntry item = work.TempoTable[i]; sout.println(" #" + i + "; c" + item.Clock + "; T" + item.Tempo + "; t" + (60e6 / item.Tempo) + "; sec" + item.Time); } #endif // R用音符のテンプレート int PBTYPE = 5; UstEvent template = new UstEvent(); template.setLyric("R"); template.setNote(60); template.setPreUtterance(0); template.setVoiceOverlap(0); template.setIntensity(100); template.setModuration(0); // 再生秒時をとりあえず無視して,ゲートタイム基準で音符を追加 UstTrack track_add = new UstTrack(); last_clock = 0; int index = 0; foreach (var item in vsq_track.getNoteEventIterator()) { if (last_clock < item.Clock) { // ゲートタイム差あり,Rを追加 UstEvent itemust = (UstEvent)template.clone(); itemust.setLength(item.Clock - last_clock); itemust.Index = index; index++; id_map[itemust.Index] = -1; track_add.addEvent(itemust); } UstEvent item_add = (UstEvent)item.UstEvent.clone(); item_add.setLength(item.ID.getLength()); item_add.setLyric(item.ID.LyricHandle.L0.Phrase); item_add.setNote(item.ID.Note); item_add.Index = index; id_map[item_add.Index] = item.InternalID; if (item.UstEvent.getEnvelope() != null) { item_add.setEnvelope((UstEnvelope)item.UstEvent.getEnvelope().clone()); } index++; track_add.addEvent(item_add); last_clock = item.Clock + item.ID.getLength(); } // テンポを格納(イベント数はあっているはず) if (track_add.getEventCount() > 0) { int size = track_add.getEventCount(); int lasttempo = -1; // ありえない値にしておく for (int i = 0; i < size; i++) { TempoTableEntry item = tempo[i]; if (lasttempo != item.Tempo) { // テンポ値が変わっているもののみ追加 UstEvent ue = track_add.getEvent(i); ue.setTempo((float)(60e6 / item.Tempo)); lasttempo = item.Tempo; m_tempo_table.Add(item); } } } else { // tempoはどうせ破棄されるのでクローンしなくていい m_tempo_table.Add(tempo[0]); } // ピッチを反映 // まず絶対ピッチを取得 VsqBPList abs_pit = new VsqBPList("", 600000, 0, 1280000); VsqBPList cpit = vsq_track.getCurve("pit"); int clock = 0; int search_indx = 0; int pit_size = cpit.size(); foreach (var item in track_add.getNoteEventIterator()) { int c = clock; int len = item.getLength(); clock += len; if (item.getLyric() == "R") { continue; } // 音符の先頭のpitは必ず入れる abs_pit.add(c, (int)(item.getNote() * 10000 + vsq_track.getPitchAt(c) * 100)); // c~c+lenまで for (int i = search_indx; i < pit_size; i++) { int c2 = cpit.getKeyClock(i); if (c < c2 && c2 < clock) { abs_pit.add(c2, (int)(item.getNote() * 10000 + vsq_track.getPitchAt(c2) * 100)); search_indx = i; } else if (clock <= c2) { break; } } } // ピッチをピッチベンドに変換しながら反映 clock = 0; foreach (var item in track_add.getNoteEventIterator()) { double sec_at_clock = tempo.getSecFromClock(clock); double sec_pre = item.getPreUtterance() / 1000.0; double sec_stp = item.getStartPoint() / 1000.0; double sec_at_begin = sec_at_clock - sec_pre - sec_stp; int clock_begin = (int)tempo.getClockFromSec(sec_at_begin); // 音符先頭との距離がPBTYPEの倍数になるようにする clock_begin -= (clock < clock_begin) ? ((clock_begin - clock) % PBTYPE) : ((clock - clock_begin) % PBTYPE); // clock_beginがsec_at_beginより前方になるとNGなので修正する double sec_at_clock_begin = tempo.getSecFromClock(clock_begin); while (sec_at_clock_begin < sec_at_begin) { clock_begin += PBTYPE; sec_at_clock_begin = tempo.getSecFromClock(clock_begin); } int clock_end = clock + item.getLength(); List <float> pitch = new List <float>(); bool allzero = true; ByRef <int> ref_indx = new ByRef <int>(0); for (int cl = clock_begin; cl < clock_end; cl += PBTYPE) { int abs = abs_pit.getValue(cl, ref_indx); float pit = (float)(abs / 100.0) - item.getNote() * 100; if (pit != 0.0) { allzero = false; } pitch.Add(pit); } if (!allzero) { item.setPitches(PortUtil.convertFloatArray(pitch.ToArray())); item.setPBType(PBTYPE); } else { item.setPBType(-1); } clock += item.getLength(); } m_tracks.Add(track_add); }
/// <summary> /// クレッシェンド,デクレッシェンド,および強弱記号をダイナミクスカーブに反映させます. /// この操作によって,ダイナミクスカーブに設定されたデータは全て削除されます. /// </summary> public void reflectDynamics() { VsqBPList dyn = getCurve("dyn"); dyn.clear(); for (Iterator <VsqEvent> itr = getDynamicsEventIterator(); itr.hasNext();) { VsqEvent item = itr.next(); IconDynamicsHandle handle = item.ID.IconDynamicsHandle; if (handle == null) { continue; } int clock = item.Clock; int length = item.ID.getLength(); if (handle.isDynaffType()) { // 強弱記号 dyn.add(clock, handle.getStartDyn()); } else { // クレッシェンド,デクレッシェンド int start_dyn = dyn.getValue(clock); // 範囲内のアイテムを削除 int count = dyn.size(); for (int i = count - 1; i >= 0; i--) { int c = dyn.getKeyClock(i); if (clock <= c && c <= clock + length) { dyn.removeElementAt(i); } else if (c < clock) { break; } } VibratoBPList bplist = handle.getDynBP(); if (bplist == null || (bplist != null && bplist.getCount() <= 0)) { // カーブデータが無い場合 double a = 0.0; if (length > 0) { a = (handle.getEndDyn() - handle.getStartDyn()) / (double)length; } int last_val = start_dyn; for (int i = clock; i < clock + length; i++) { int val = start_dyn + (int)(a * (i - clock)); if (val < dyn.getMinimum()) { val = dyn.getMinimum(); } else if (dyn.getMaximum() < val) { val = dyn.getMaximum(); } if (last_val != val) { dyn.add(i, val); last_val = val; } } } else { // カーブデータがある場合 int last_val = handle.getStartDyn(); int last_clock = clock; int bpnum = bplist.getCount(); int last = start_dyn; // bplistに指定されている分のデータ点を追加 for (int i = 0; i < bpnum; i++) { VibratoBPPair point = bplist.getElement(i); int pointClock = clock + (int)(length * point.X); if (pointClock <= last_clock) { continue; } int pointValue = point.Y; double a = (pointValue - last_val) / (double)(pointClock - last_clock); for (int j = last_clock; j <= pointClock; j++) { int val = start_dyn + (int)((j - last_clock) * a); if (val < dyn.getMinimum()) { val = dyn.getMinimum(); } else if (dyn.getMaximum() < val) { val = dyn.getMaximum(); } if (val != last) { dyn.add(j, val); last = val; } } last_val = point.Y; last_clock = pointClock; } // bplistの末尾から,clock => clock + lengthまでのデータ点を追加 int last2 = last; if (last_clock < clock + length) { double a = (handle.getEndDyn() - last_val) / (double)(clock + length - last_clock); for (int j = last_clock; j < clock + length; j++) { int val = last2 + (int)((j - last_clock) * a); if (val < dyn.getMinimum()) { val = dyn.getMinimum(); } else if (dyn.getMaximum() < val) { val = dyn.getMaximum(); } if (val != last) { dyn.add(j, val); last = val; } } } } } } }
public static bool Edit(cadencii.vsq.VsqFile vsq) { // 選択されているアイテム(のInternalID)をリストアップ System.Collections.Generic.List <int> ids = new System.Collections.Generic.List <int>(); for (Iterator <SelectedEventEntry> itr = AppManager.itemSelection.getEventIterator(); itr.hasNext();) { SelectedEventEntry entry = itr.next(); ids.Add(entry.original.InternalID); } cadencii.vsq.VsqTrack track = vsq.Track.get(AppManager.getSelected()); // コントロールカーブの時間方向の解像度を,Cadenciiの設定値から取得 int resol = AppManager.editorConfig.getControlCurveResolutionValue(); for (int i = 0; i < ids.Count; i++) { int internal_id = ids[i]; for (Iterator <VsqEvent> itr = track.getNoteEventIterator(); itr.hasNext();) { cadencii.vsq.VsqEvent item = itr.next(); // 指定されたInternalIDと同じなら,編集する if (item.InternalID == internal_id) { // Brightnessカーブを取得 cadencii.vsq.VsqBPList bri = track.getCurve("BRI"); // 音符の最後の位置でのBRIを取得.処理の最後で追加 int value_at_end = bri.getValue(item.Clock + item.ID.Length); // これから編集しようとしている範囲にすでに値がある場合,邪魔なので削除する for (Iterator <int> itr2 = bri.keyClockIterator(); itr.hasNext();) { int clock = itr2.next(); System.Console.WriteLine("clock=" + clock); if (item.Clock <= clock && clock <= item.Clock + item.ID.Length) { itr2.remove(); } } // 直前に指定したBRI値.最初はありえない値にしておく int last_v = -1; // 時間方向解像度(resol)ごとのクロックに対して,順次BRIを設定 for (int clock = item.Clock; clock <= item.Clock + item.ID.Length; clock += resol) { // BRIを取得.x=0が音符の先頭,x=1が音符の末尾.getCurve関数は,この仕様を満たすようにBRIを返すように,お好みで定義 float x = (clock - item.Clock) / (float)item.ID.Length; int v = getCurve(x); if (last_v != v) { // 直前に指定した値と違うときだけ追加. bri.add(clock, v); } // 「直前の値」を更新 last_v = v; } // 音符末尾の位置のBRIを強制的に元の値に戻す.これをやらないと, // その音符の末尾以降のBRIがそのまま編集の影響を受けてしまう bri.add(item.Clock + item.ID.Length, value_at_end); break; } } } return(true); }
/// <summary> /// このトラックの指定した範囲を削除し,削除範囲以降の部分を削除開始位置までシフトします /// </summary> /// <param name="clock_start"></param> /// <param name="clock_end"></param> public void removePart(int clock_start, int clock_end) { int dclock = clock_end - clock_start; // 削除する範囲に歌手変更イベントが存在するかどうかを検査。 VsqEvent t_last_singer = null; for (Iterator <VsqEvent> itr = getSingerEventIterator(); itr.hasNext();) { VsqEvent ve = itr.next(); if (clock_start <= ve.Clock && ve.Clock < clock_end) { t_last_singer = ve; } if (ve.Clock == clock_end) { t_last_singer = null; // 後でclock_endの位置に補うが、そこにに既に歌手変更イベントがあるとまずいので。 } } VsqEvent last_singer = null; if (t_last_singer != null) { last_singer = (VsqEvent)t_last_singer.clone(); last_singer.Clock = clock_end; } bool changed = true; // イベントの削除 while (changed) { changed = false; int numEvents = getEventCount(); for (int i = 0; i < numEvents; i++) { VsqEvent itemi = getEvent(i); if (clock_start <= itemi.Clock && itemi.Clock < clock_end) { removeEvent(i); changed = true; break; } } } // クロックのシフト if (last_singer != null) { addEvent(last_singer); //歌手変更イベントを補う } int num_events = getEventCount(); for (int i = 0; i < num_events; i++) { VsqEvent itemi = getEvent(i); if (clock_end <= itemi.Clock) { itemi.Clock -= dclock; } } for (int i = 0; i < VsqTrack.CURVES.Length; i++) { string curve = VsqTrack.CURVES[i]; VsqBPList bplist = getCurve(curve); if (bplist == null) { continue; } VsqBPList buf_bplist = (VsqBPList)bplist.clone(); bplist.clear(); int value_at_end = buf_bplist.getValue(clock_end); bool at_end_added = false; foreach (var key in buf_bplist.keyClockIterator()) { if (key < clock_start) { bplist.add(key, buf_bplist.getValue(key)); } else if (clock_end <= key) { if (key == clock_end) { at_end_added = true; } bplist.add(key - dclock, buf_bplist.getValue(key)); } } if (!at_end_added) { bplist.add(clock_end - dclock, value_at_end); } } }