lipsync/Boare.Lib.Vsq/UstFile.cs
2024-05-20 00:17:44 +00:00

488 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* UstFile.cs
* Copyright (c) 2009 kbinani, PEX
*
* This file is part of Boare.Lib.Vsq.
*
* Boare.Lib.Vsq is free software; you can redistribute it and/or
* modify it under the terms of the BSD License.
*
* Boare.Lib.Vsq 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.
*/
#if JAVA
package org.kbinani.vsq;
import java.util.*;
import java.io.*;
import org.kbinani.*;
#else
using System;
using bocoree;
using bocoree.java.util;
using bocoree.java.io;
namespace Boare.Lib.Vsq {
using Float = System.Single;
using boolean = System.Boolean;
#endif
#if JAVA
public class UstFile implements Cloneable {
#else
public class UstFile : ICloneable {
#endif
/// <summary>
/// [#PREV]が指定されているUstEventのIndex
/// </summary>
public const int PREV_INDEX = int.MinValue;
/// <summary>
/// [#NEXT]が指定されているUstEventのIndex
/// </summary>
public const int NEXT_INDEX = int.MaxValue;
public Object Tag;
private float m_tempo = 120.00f;
private String m_project_name = "";
private String m_voice_dir = "";
private String m_out_file = "";
private String m_cache_dir = "";
private String m_tool1 = "";
private String m_tool2 = "";
private Vector<UstTrack> m_tracks = new Vector<UstTrack>();
private Vector<TempoTableEntry> m_tempo_table;
public UstFile( String path ) {
BufferedReader sr = null;
try {
sr = new BufferedReader( new InputStreamReader( new FileInputStream( path ), "Shift_JIS" ) );
#if DEBUG
bocoree.debug.push_log( "path=" + path );
bocoree.debug.push_log( "(sr==null)=" + (sr == null) );
#endif
String line = sr.readLine();
if ( !line.Equals( "[#SETTING]" ) ) {
throw new Exception( "invalid ust file" );
}
UstTrack track = new UstTrack();
int type = 0; //0 => reading "SETTING" section
while ( true ) {
#if DEBUG
PortUtil.println( "line=" + line );
#endif
UstEvent ue = null;
if ( type == 1 ) {
ue = new UstEvent();
}
int index = 0;
if ( line.Equals( "[#TRACKEND]" ) ) {
break;
} else if ( line.ToUpper().Equals( "[#NEXT]" ) ) {
index = NEXT_INDEX;
} else if ( line.ToUpper().Equals( "[#PREV]" ) ) {
index = PREV_INDEX;
} else {
String s = line.Replace( "[#", "" ).Replace( "]", "" ).Trim();
try {
index = PortUtil.parseInt( s );
} catch ( Exception ex ) {
#if DEBUG
PortUtil.println( "UstFile#.ctor; ex=" + ex );
#endif
}
}
#if DEBUG
bocoree.debug.push_log( "index=" + index );
#endif
line = sr.readLine(); // "[#" 直下の行
if ( line == null ) {
break;
}
while ( !line.StartsWith( "[#" ) ) {
#if DEBUG
PortUtil.println( "line=" + line );
#endif
String[] spl = PortUtil.splitString( line, new char[] { '=' }, 2 );
if ( type == 0 ) {
// reading "SETTING" section
if ( spl[0].Equals( "Tempo" ) ) {
m_tempo = 125f;
float v = 125f;
try {
v = PortUtil.parseFloat( spl[1] );
m_tempo = v;
} catch ( Exception ex ) {
}
} else if ( spl[0].Equals( "ProjectName" ) ) {
m_project_name = spl[1];
} else if ( spl[0].Equals( "VoiceDir" ) ) {
m_voice_dir = spl[1];
} else if ( spl[0].Equals( "OutFile" ) ) {
m_out_file = spl[1];
} else if ( spl[0].Equals( "CacheDir" ) ) {
m_cache_dir = spl[1];
} else if ( spl[0].Equals( "Tool1" ) ) {
m_tool1 = spl[1];
} else if ( spl[0].Equals( "Tool2" ) ) {
m_tool2 = spl[1];
}
} else if ( type == 1 ) {
// readin event section
if ( spl[0].Equals( "Length" ) ) {
ue.setLength( 0 );
int v = 0;
try {
v = PortUtil.parseInt( spl[1] );
ue.setLength( v );
} catch ( Exception ex ) {
}
} else if ( spl[0].Equals( "Lyric" ) ) {
ue.Lyric = spl[1];
} else if ( spl[0].Equals( "NoteNum" ) ) {
ue.Note = 0;
int v = 0;
try {
v = PortUtil.parseInt( spl[1] );
ue.Note = v;
} catch ( Exception ex ) {
}
} else if ( spl[0].Equals( "Intensity" ) ) {
ue.Intensity = 64;
int v = 64;
try {
v = PortUtil.parseInt( spl[1] );
ue.Intensity = v;
} catch ( Exception ex ) {
}
} else if ( spl[0].Equals( "PBType" ) ) {
ue.PBType = 5;
int v = 5;
try {
v = PortUtil.parseInt( spl[1] );
ue.PBType = v;
} catch ( Exception ex ) {
}
} else if ( spl[0].Equals( "Piches" ) ) {
String[] spl2 = PortUtil.splitString( spl[1], ',' );
float[] t = new float[spl2.Length];
for ( int i = 0; i < spl2.Length; i++ ) {
float v = 0;
try {
v = PortUtil.parseFloat( spl2[i] );
t[i] = v;
} catch ( Exception ex ) {
}
}
ue.Pitches = t;
} else if ( spl[0].Equals( "Tempo" ) ) {
ue.Tempo = 125f;
float v;
try {
v = PortUtil.parseFloat( spl[1] );
ue.Tempo = v;
} catch ( Exception ex ) {
}
} else if ( spl[0].Equals( "VBR" ) ) {
ue.Vibrato = new UstVibrato( line );
/*
PBW=50,50,46,48,56,50,50,50,50
PBS=-87
PBY=-15.9,-20,-31.5,-26.6
PBM=,s,r,j,s,s,s,s,s
*/
} else if ( spl[0].Equals( "PBW" ) || spl[0].Equals( "PBS" ) || spl[0].Equals( "PBY" ) || spl[0].Equals( "PBM" ) ) {
if ( ue.Portamento == null ) {
ue.Portamento = new UstPortamento();
}
ue.Portamento.ParseLine( line );
} else if ( spl[0].Equals( "Envelope" ) ) {
ue.Envelope = new UstEnvelope( line );
//PreUtterance=1
//VoiceOverlap=6
} else if ( spl[0].Equals( "VoiceOverlap" ) ) {
if ( spl[1] != "" ) {
ue.VoiceOverlap = PortUtil.parseInt( spl[1] );
}
} else if ( spl[0].Equals( "PreUtterance" ) ) {
if ( spl[1] != "" ) {
ue.PreUtterance = PortUtil.parseInt( spl[1] );
}
} else if ( spl[0].Equals( "Flags" ) ) {
ue.Flags = line.Substring( 6 );
} else {
#if DEBUG
PortUtil.println( "UstFile#.ctor; info: don't know how to process this line; line=" + line );
#endif
}
}
if ( !sr.ready() ) {
break;
}
line = sr.readLine();
}
#if DEBUG
bocoree.debug.push_log( "(ue==null)=" + (ue == null) );
#endif
if ( type == 0 ) {
type = 1;
} else if ( type == 1 ) {
ue.Index = index;
track.addEvent( ue );
}
}
m_tracks.add( track );
updateTempoInfo();
} catch ( Exception ex ) {
#if DEBUG
bocoree.debug.push_log( "ex=" + ex );
#endif
} finally {
if ( sr != null ) {
try {
sr.close();
} catch ( Exception ex2 ) {
}
}
}
}
/// <summary>
/// vsqの指定したトラックを元にトラックを1つだけ持つustを構築します
/// </summary>
/// <param name="vsq"></param>
/// <param name="track_index"></param>
public UstFile( VsqFile vsq, int track_index ) {
VsqTrack track = vsq.Track.get( track_index );
// デフォルトのテンポ
if( vsq.TempoTable.size() <= 0 ){
m_tempo = 120.0f;
}else{
m_tempo = (float)(60e6 / (double)vsq.TempoTable.get( 0 ).Tempo);
}
updateTempoInfo();
// R用音符のテンプレート
int PBTYPE = 5;
UstEvent template = new UstEvent();
template.Lyric = "R";
template.Note = 60;
template.PreUtterance = 0;
template.VoiceOverlap = 0;
template.Intensity = 100;
template.Moduration = 0;
// 再生秒時をとりあえず無視して,ゲートタイム基準で音符を追加
UstTrack track_add = new UstTrack();
int last_clock = 0;
int index = 0;
for( Iterator itr = track.getNoteEventIterator(); itr.hasNext(); ){
VsqEvent item = (VsqEvent)itr.next();
if( last_clock < item.Clock ){
// ゲートタイム差ありRを追加
UstEvent itemust = (UstEvent)template.clone();
itemust.setLength( item.Clock - last_clock );
itemust.Index = index;
index++;
track_add.addEvent( itemust );
}
UstEvent item_add = new UstEvent();
item_add.setLength( item.ID.getLength() );
item_add.Lyric = item.ID.LyricHandle.L0.Phrase;
item_add.Note = item.ID.Note;
item_add.Index = index;
item_add.Intensity = item.ID.Dynamics;
item_add.Moduration = item.UstEvent.Moduration;
item_add.PreUtterance = item.UstEvent.PreUtterance;
item_add.VoiceOverlap = item.UstEvent.VoiceOverlap;
index++;
track_add.addEvent( item_add );
last_clock = item.Clock + item.ID.getLength();
}
// 再生秒時を無視して,ピッチベンドを追加
//VsqBPList pbs = track.getCurve( "pbs" );
//VsqBPList pit = track.getCurve( "pit" );
int clock = 0;
for ( Iterator itr = track_add.getNoteEventIterator(); itr.hasNext(); ) {
UstEvent item = (UstEvent)itr.next();
int clock_begin = clock;
int clock_end = clock + item.getLength();
Vector<Float> pitch = new Vector<Float>();
boolean allzero = true;
for ( int cl = clock_begin; cl <= clock_end; cl += PBTYPE ) {
float pit = (float)track.getPitchAt( cl );
if ( pit != 0.0 ) {
allzero = false;
}
pitch.add( pit );
}
if ( !allzero ) {
item.Pitches = PortUtil.convertFloatArray( pitch.toArray( new Float[] { } ) );
item.PBType = PBTYPE;
} else {
item.PBType = -1;
}
clock += item.getLength();
}
// 再生秒時を考慮して,適時テンポを追加
//TODO: このへん
// throw new NotImplementedException();
m_tracks.add( track_add );
}
private UstFile() {
}
public String getProjectName() {
return m_project_name;
}
public int getBaseTempo() {
return (int)(6e7 / m_tempo);
}
public double getTotalSec() {
int max = 0;
for ( int track = 0; track < m_tracks.size(); track++ ) {
int count = 0;
for ( int i = 0; i < m_tracks.get( track ).getEventCount(); i++ ) {
count += (int)m_tracks.get( track ).getEvent( i ).getLength();
}
max = Math.Max( max, count );
}
return getSecFromClock( max );
}
public Vector<TempoTableEntry> getTempoList() {
return m_tempo_table;
}
public UstTrack getTrack( int track ) {
return m_tracks.get( track );
}
public int getTrackCount() {
return m_tracks.size();
}
/// <summary>
/// TempoTableの[*].Timeの部分を更新します
/// </summary>
/// <returns></returns>
public void updateTempoInfo() {
m_tempo_table = new Vector<TempoTableEntry>();
if ( m_tracks.size() <= 0 ) {
return;
}
int clock = 0;
double time = 0.0;
int last_tempo_clock = 0; //最後にTempo値が代入されていたイベントのクロック
float last_tempo = m_tempo; //最後に代入されていたテンポの値
for ( int i = 0; i < m_tracks.get( 0 ).getEventCount(); i++ ) {
if ( m_tracks.get( 0 ).getEvent( i ).Tempo > 0f ) {
time += (clock - last_tempo_clock) / (8.0 * last_tempo);
if ( m_tempo_table.size() == 0 && clock != 0 ) {
m_tempo_table.add( new TempoTableEntry( 0, (int)(6e7 / m_tempo), 0.0 ) );
}
m_tempo_table.add( new TempoTableEntry( clock, (int)(6e7 / m_tracks.get( 0 ).getEvent( i ).Tempo), time ) );
last_tempo = m_tracks.get( 0 ).getEvent( i ).Tempo;
last_tempo_clock = clock;
}
clock += (int)m_tracks.get( 0 ).getEvent( i ).getLength();
}
}
/// <summary>
/// 指定したクロックにおける、clock=0からの演奏経過時間(sec)
/// </summary>
/// <param name="clock"></param>
/// <returns></returns>
public double getSecFromClock( int clock ) {
int c = m_tempo_table.size();
for ( int i = c - 1; i >= 0; i-- ) {
TempoTableEntry item = m_tempo_table.get( i );
if ( item.Clock < clock ) {
double init = item.Time;
int dclock = clock - item.Clock;
double sec_per_clock1 = item.Tempo * 1e-6 / 480.0;
return init + dclock * sec_per_clock1;
}
}
double sec_per_clock = 0.125 / m_tempo;
return clock * sec_per_clock;
}
public void write( String file ) {
BufferedWriter sw = null;
try {
sw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file ), "Shift_JIS" ) );
sw.write( "[#SETTING]" );
sw.newLine();
sw.write( "Tempo=" + m_tempo );
sw.newLine();
sw.write( "Tracks=1" );
sw.newLine();
sw.write( "ProjectName=" + m_project_name );
sw.newLine();
sw.write( "VoiceDir=" + m_voice_dir );
sw.newLine();
sw.write( "OutFile=" + m_out_file );
sw.newLine();
sw.write( "CacheDir=" + m_cache_dir );
sw.newLine();
sw.write( "Tool1=" + m_tool1 );
sw.newLine();
sw.write( "Tool2=" + m_tool2 );
sw.newLine();
UstTrack target = m_tracks.get( 0 );
int count = target.getEventCount();
for ( int i = 0; i < count; i++ ) {
target.getEvent( i ).print( sw );
}
sw.write( "[#TRACKEND]" );
sw.newLine();
} catch ( Exception ex ) {
} finally {
if ( sw != null ) {
try {
sw.close();
} catch ( Exception ex2 ) {
}
}
}
}
public Object clone() {
UstFile ret = new UstFile();
ret.m_tempo = m_tempo;
ret.m_project_name = m_project_name;
ret.m_voice_dir = m_voice_dir;
ret.m_out_file = m_out_file;
ret.m_cache_dir = m_cache_dir;
ret.m_tool1 = m_tool1;
ret.m_tool2 = m_tool2;
for ( int i = 0; i < m_tracks.size(); i++ ) {
ret.m_tracks.set( i, (UstTrack)m_tracks.get( i ).clone() );
}
ret.m_tempo_table = new Vector<TempoTableEntry>();
for ( int i = 0; i < m_tempo_table.size(); i++ ) {
ret.m_tempo_table.add( (TempoTableEntry)m_tempo_table.get( i ).clone() );
}
return ret;
}
#if !JAVA
public object Clone() {
return clone();
}
#endif
}
#if !JAVA
}
#endif