/* * 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 /// /// [#PREV]が指定されているUstEventのIndex /// public const int PREV_INDEX = int.MinValue; /// /// [#NEXT]が指定されているUstEventのIndex /// 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 m_tracks = new Vector(); private Vector 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 ) { } } } } /// /// vsqの指定したトラックを元に,トラックを1つだけ持つustを構築します /// /// /// 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 pitch = new Vector(); 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 getTempoList() { return m_tempo_table; } public UstTrack getTrack( int track ) { return m_tracks.get( track ); } public int getTrackCount() { return m_tracks.size(); } /// /// TempoTableの[*].Timeの部分を更新します /// /// public void updateTempoInfo() { m_tempo_table = new Vector(); 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(); } } /// /// 指定したクロックにおける、clock=0からの演奏経過時間(sec) /// /// /// 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(); 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