#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"

# tk_chooseDirectoryのエラー（仕様）。フォルダ名入力欄を編集した内容が正しく反映されない。

# 無音追加
# 自動推定はF0を参照してた？
# aiffをwavに変換
# reclist.txtで""を使えるように("a あ"など。OREMOの実装のことのよう)

# 2.0-b120710
# - (追加) 波形画面の右クリックメニューに単独音パラメータ自動推定とゼロクロス補正を追加した。(PopUpMenu)
# - (変更) 単独音パラメータ自動推定を選択範囲に対して実行した場合は設定窓を自動で消さないようにした(estimateParam)
# - (追加) 一覧表の右クリックメニューの随所に水平線（区切り線）を入れた。
# - (追加) 各パラメータをピッチマーク左側の右上がりゼロクロス位置にスナップする機能を追加した(zeroCross、doZeroCross)
# - (追加) オーバーラップの自動推定を追加した(estimateParam, doEstimateParam)
#          自動推定ovl。対象は、、うぃ、うぇ、うぉ、行：さ、ざ、な、は、ま、や、ら、わ
#          これらの音のovlを、先行発声*0.46する。
# - (修正) 推定対象のチェックを外したときに出るバグを修正(doEstimateParam)
# - (変更) wavに"_"を付けた後の通知窓で、oto.iniの保存をうながすようにした。
# - (変更) MacOS、OREMOでも利用できるコードにした（ごく一部）。
# - (変更) 波形表示の縦軸を固定化した。縮尺を変えたり以前の自動縮尺にする場合は
#          「詳細設定」の「縦軸最大値」を変更する。(settings, Redraw)

# 2.0-b120309
# - (修正) 定期バックアップ時の細かい修正（backup.iniを保存したことを最下部に表示しないようにするなど）。(saveParamFile)
# - (修正) 表示文の間違い（FFTの窓長の単位など）を修正した。
# - (追加) 詳細設定で、F0ターゲット値に合わせて他の設定値を自動設定するボタンを追加した。(autoF0settings)
# - (追加) 上部「表示」メニューに「横軸の拡大」「波形を表示」「メトロノームを表示」（右クリックメニューにのみあったもの）を追加した。
# - (変更) 上部メニューの随所に水平線（区切り線）を入れた。
# - (変更) 上部「ツール」メニューに「wavファイルの一括編集」のサブメニューを追加して関係メニューを移動した。
# - (変更) 起動時にbackup.iniがあった場合(前回異常終了した場合)、そのファイルのあるフォルダを表示して終了するようにした。(autoBackup)
# - (変更) 起動時にbackup.iniがあった場合(前回異常終了した場合)、警告窓が出る前に音源フォルダを開かないようにした。
# - (変更) F0最高値のデフォルト値を400から800に引き上げた。

# 2.0-b120301
# - (追加) 簡易な定期バックアップ機能を追加した。5分おきに、setParam.exeのあるフォルダのbackup.iniにその時点の原音設定を上書きする。(autoBackup)
# - (追加) パラメータ検索で完全一致と部分一致を選択できるようにした。(searchParam)
# - (変更) キャッシュファイル名を変更した(パラメータファイル名.oremo-Scache→パラメータファイル名.setParam-Scache) (saveCacheFile, readCacheFile)
# - (修正) 各メッセージを英語に翻訳した場合に起動できなくなるバグ([]があるとエラーを起こす)を修正した(doReadInitFile)

# 2.0-b120124
# - (追加) エイリアス一括変換で、重複上限数を超えたエントリを削除する機能を追加した。(changeAlias, doTransAlias)
# - (追加) プログレスバーのタイトルを引数で指定できるようにした。(updateProgressWindow)
# - (追加) MFCCを用いた連続音の先行発声の自動推定機能を追加した。(autoAdjustRen2)
# - (追加) エイリアス補完に「ぢ」「づ」の補完を追加した。(woComplement)
# - (修正) 連続音のパラメータ自動生成で、ファイル名に平仮名片仮名が無い場合にエントリが消えるバグを修正した。(doGenParam)
# - (修正) 連続音でF0表示が消えるバグを修正した。(Redraw)

# 2.0-b111229
# - (追加) wavファイルを一括モノラル化する機能を追加した(convertMonoAll)。      
# - (修正) ステレオwavの発声タイミングチェック再生(F8)ができないバグを修正した
#          →wavを読み込む際にモノラル化するようにした。

# 2.0-b111201
# - (追加) wav以外の波形形式に対応した。setParam-setting.iniのwaveFileExt=wavをaiffに書き換える。mp3も読めるが現時点ではあまり意味がない。
#          一つのフォルダ内の波形ファイルは一種類のフォーマットで統一されていることが前提。混在していると想定しない動作をする。
# - (追加) ファイルメニューから最近使ったフォルダ等を開く機能を追加した。
# - (追加) メイン窓最下行に幾つ目の音を編集しているかを表示するようにした(jumpRec)
# - (追加) 左右ブランクの移動可能範囲を他パラメータより優先するモードを追加した。デフォルトで有効。(setParam)
# - (追加) エイリアス一括変換の変換規則に音名(%m)、接尾辞(%s)、重複通し番号(%r)を追加した。(doTransAlias)
#          それに伴い設定窓の説明内容を変更した。
# - (変更) オプションメニュー構成を変更した。「マウス、キー操作の設定」という項目にいくつかを移動した。
# - (変更) 先行発声のタイミング試聴で、発声開始がなるべくクリック音の1拍または半拍に合うようにした(playUttTiming)
# - (変更) initProgressWindowのウィジェット参照を修正した（実行内容に変化は無し）
# - (変更) 保存フォルダの初期値を"c:/Program Files/UTAU/voice"または"c:/Program Files (x86)/UTAU/voice"にした。
# - (変更) 起動時にフォルダ選択窓を出さないようにした。 
#          元に戻したい場合はsetParam-init.tclで set startup(makeRecListFromDir) 1; set startup(choosesaveDir) 1する。
# - (修正) オーバーラップ値を負にしない設定で左ブランクを移動させると負にできてしまう不具合を修正した。
# - (修正) oto.iniの読み込みを高速化した(読み込み中に波形描画をしないようにした)
#          ↑波形長をその都度[snd length]で求めず$v(sndLength)を参照するように全体的に変更を行った
# - (修正) tabを押すとF1などのキーバインドが効かなくなるバグを修正(setParam.tcl)
# - (修正) 空のwavファイルを読んだ際にエラー窓が出ないようにした(readWavFile、changeWidth、zoomX)

# 2.0-b111007
# - (追加) 指定した範囲を再生する機能を追加した。Ctrl+F1〜F5の5種類を設定可能(playRange)
# - (追加) 連続音のパラメータ自動生成で、wavファイル名の途中に"_"が入っているときは1モーラ分の休符と
#          みなすようにした。「_あ_い.wav」ならエイリアスは「- あ」「- い」になる。(doGenParamなど)

# 2.0-b110727
# - (追加) 連続音のパラメータ自動生成に先行発声位置の自動推定機能を追加した(autoAdjustRen)
# - (追加) 「を」や「a を」等が無い場合「お」で補完する機能(woComplement)
# - (追加) 単独音併用の連続音の際に不足したエイリアスを単独音で補完する機能(aliasComplement)
# - (追加) Alt+マウスホイールで次のwavに移動するようにした(Alt+上下矢印と同じ。連続音向け)
# - (追加) wキーで波形画面の先頭に移動するようにした
# - (変更) ツールメニューの自動推定関係を一つにまとめた
# - (修正) 設定窓等を開いて一覧表のフォーカスが消えた際に、波形画面のホイールスクロールが
#          効かなくなるエラーを修正
# - (修正) 連続音リストを作る際に平仮名片仮名以外の文字(↑など)を無視するようにした
# - (修正) 連続音をモーラに分解する周りの処理を少し書き換えた

# 2.0-b110709
# - (追加) 単独音自動推定を指定したセルのみに適用できるようにした
# - (追加) 単独音自動推定を一覧表の右クリックメニューやフォルダ選択後メニューに追加した
# - (追加) 単独音の先行発声の自動推定を追加した(estimateParam)
# - (追加) 単独音の自動推定でオーバーラップ=0にするようにした(estimateParam)
# - (変更) 単独音自動推定の子音部、右ブランク推定規則を変更した
# - (修正) 一覧表窓でESCを押すと窓が閉じるバグを修正した
# - (修正) タイミングチェック試聴で20より多く指定した場合にエラー窓が出ないエラーを修正した(playUttTiming)

# 2.0-b110629
# - (修正) 各ファイルopenエラーをcatchするようにした。
# - (修正) 日本語フォルダを指定していた際の保存フォルダ変更に関するバグ修正(choosesaveDir)
# - (変更) メッセージ文を若干変更した(message/proc-text.tcl)
# - (修正) オーディオI/O設定窓でエラーが出る問題を修正した(ioSettings)
# - (追加) オーディオI/O設定窓下部に警告文を追加した
# - (追加) ESCで各種設定窓を閉じるようにした

# 2.0-b101009
# - (追加) 配列の手動アンドゥ＆リドゥを追加した(setUndoPoint, undoParam, redoParam)
# - (追加) 値の一括変更機能(changeCell)に、セルの制限条件を付けられるようにした（選択範囲のセルであり、かつ条件を満たすセルのみを一括変更する）。
# - (追加) テスト中の単独音用パラメータ推定に、子音部の最小値を指定できるようにした。
# - (追加) オーバーラップが負になるかどうか選べるようにした(オプションメニュー)。
# - (追加) F9で左右ブランク両側をカットする機能を追加した(cutBlank、オプションメニューでON/OFF)。
# - (変更) 旧バージョンのように「常にwav全体を表示」するモードを
#          右クリックメニューから選べるようにした。
# - (変更) doCutWavを若干修正(処理の無駄を省いた…というほどでもない)。
# - (修正) 単独音のパラメータ自動推定後、保存ファイル名がoto-autoEstimation.iniにならないようにした。
# - (修正) 別の行に移動したときの描画速度を向上した。
# - (追加) 連続音のパラメータ自動生成で、カタカナに対応した。

# 2.0-b100724
# - マウスドラッグでパラメータ位置移動できるようにした
# - (追加) 初期化ファイルの読み込みを追加した(readSettings)
# - (修正) 発声タイミングチェック設定の変数の細かいバグ修正(uttTimingSettings)
# - (変更) 初期化ファイルに保存する内容を増やした(v以外の配列を追加)(saveSettings)
# - (追加) 初期設定ファイルを読み込む際に簡易サニタイジングするようにした(doReadInitFile)
# - (変更) パラメータ線描画を別procにして高速化した(RedrawParam)

# 2.0-b100509
# - (修正) 連続音パラメータ自動生成で重複エイリアスが上限を超えたファイルのパラメータ位置がずれるバグを修正(genParam)。

# 2.0-b100411
# - (追加) マウスでパラメータをずらしたときに移動できる範囲に制限を増やした(setParam)
# - (追加) 右クリックメニューに、マウス操作(オプションメニューと同じ)を追加
# - (追加) 左ブランクや先行発声を動かすとき、他パラメータも動かして
#          相対位置を保つ場合は、各々の線の上にマークを付ける(Redraw)
# - (修正) wav両端カット機能で連続音のように複数エイリアスがある場合に重複数だけ余計にカットするバグを修正。(cutWav)
# - (追加) 連続音のパラメータ自動生成で、エイリアスが重複した場合に通し番号を追記するようにした(genParam)。

# 2.0-b100410
# - (追加) 値の一括変更(ctrl+m)に小数除去機能を追加(changeCell)
# - (修正) 値の一括変更(ctrl+m)でエイリアスの欄を除外するようにした(doChangeCell)
# - (追加) 右クリックメニューに時間拡大指定を追加(PopUpMenu)
# - (変更) 時間方向のスケール拡大縮小をctrl+wheelで行うようにした。
# - (変更) マウスでパラメータをずらしたときに移動できる範囲に制限をかけた(setParam)
# - (修正) 先行発声をずらしたときに左ブランク値の小数点以下が3桁以上になるバグを修正。
# - (修正) オーディオデバイスの文字化け周りのバグ修正(setIODevice)

# 2.0-b100204
# - (追加) 波形の表示/非表示を切り替えられるようにした(toggleWaveなど)
# - (追加) 読み込み済みのパラメータに別の原音パラメータファイルをマージする(mergeParamFile)
# - (追加) 選択中の範囲の値を一括変更する機能(changeCell)
# - (追加) オーディオデバイスのレイテンシを変更する機能(ioSettings)
# - (修正) オーディオドライバ名の文字化けを若干解消(ioSettings)
# - (変更) 原音パラメータを読む際にwavが存在しないエントリは削除するようにした(readParamFile)

# 2.0-b091205
# - (追加) メイン窓にD&Dされたときの処理を追加(procDnd)
# - (追加) 発声タイミング補正切替を追加(timingAdjMode)
# - (修正) 細かいバグの修正。
# - (追加) プログレスバー表示
# - (追加) oto.ini読み込み高速化用のキャッシュ機能
# - (追加) F3やAlt-F3で他パラメータを連動してうごかせるようにした。
# - (追加) エイリアス一括変換機能を追加(changeAlias)
# - (修正) 連続音パラメータ生成直後にspaceで再生すると、表示中の波形でない波形が再生されるバグを修正。

# 2.0-b091120
# - (変更) 全メッセージを外部ファイル化。
# - (追加) wav両端の無音をカットする機能を追加 (cutWav)
# - (修正) パラメータ一覧表の数値を削除すると"0"と表示されるバグ?を修正。

# 2.0-b091104
# - (追加) 読み込み時にパラメータ生成(単独/連続音)を選択実行できるようにした。
# - (追加) 先行発声チェック用の試聴機能および設定窓を追加
# - (追加) 自動収録した連続音のパラメータ自動生成(genParam)
# - (変更) 初期化ファイル保存の保存対象を変更(saveSettings)
# - (修正) 行を複製する際にパラメータに空欄がある場合のコピーのバグを修正。

# 2.0-b091007
# - (修正) 以前作った左右ブランク自動推定を最新バージョンで動くように修正。

# 2.0-b090903
# - (追加) 左ブランク値を変更した際に、同wavファイルの他の音の左ブランク値も
#          連動して変更できるようにした。
# - (変更) 各種窓を開いたときにフォーカスするように変更。
# - (追加) パラメータ検索を実装。
# - (変更) パラメータ一覧表のタイトルが長すぎる場合は切り詰めるように変更。
# - (変更) マウス+F1〜F5で各パラメータをドラッグ可能。
# - (修正) setParamでF0が表示されないバグの修正。
# - (修正) setParamでマウスドラッグによるセル複数選択ができないバグの修正。

# 2.0-b090822
# - (修正) setParamの一覧表タイトルのファイル名表示が更新されないバグを修正。
# - (修正) setParamの一覧表の値に挿入・削除したときカーソルが末尾に行くバグを修正。
# - (変更) 全パラメータをマイクロsec精度にした。
# - (追加) 右ブランクの負の値に対応。
# - (追加) オプションで右ブランクの正負を切り替えられるようにした。
# - (追加) オプションで左ブランクの変更時の他パラメータのふるまいを
#          切り替えられるようにした
# (2.0-b090813)
# - (変更) リストスクロールで２つ前後の音が見えるようにした。
# - (追加) 初期化ファイルを生成できるようにした。

# 2.0-b090803
# - (修正) readParamFile。oto.iniにエントリが足りない場合のバグを修正。
# -（追加) ツールメニューにDC成分一括除去を追加
# -（追加) ツールメニューにwavファイル名変更（冒頭に"_"を付ける)を追加
# - (追加) リストボックスの横幅をctrl+wheelで変更可能にした。

# 2.0-b090727
# - (変更) setParamで波形窓にエイリアスを表示。
# - (変更) 一覧表タイトルにファイル名を表示。
# - (変更) 一覧表の上下矢印移動で表の上端・下端でワープしないようにした。
# - (変更) ガイドBGM設定窓で、BGM試聴、録音イメージ音試聴ボタンを追加。
# - (変更) オーディオI/O設定窓に説明文を表示。
# - (修正) 自動録音(loop)で、音名リスト末尾までいったら終了するようにした。
# - (変更) Redrawの演算回数を少し削減。
# - (修正) makeRecListFromDirでのファイル名登録のバグを修正。

# 2.0-b090724
# - (修正) val2sampで実数値を返すことがあるバグを修正。

# 2.0-b090719
# - (修正) メトロノーム再生を停止できなかったバグを修正。

# 2.0-b090715
# - saveParamFile 高速化(paramUの内容を直接書き出すようにした)

# 2.0-b090706
# - oremo.tcl 本体のサブルーチン集を別ファイルに移行。

#---------------------------------------------------
# サブルーチン

#---------------------------------------------------
# メイン画面表示をリセットする？
#
proc resetDisplay {} {
  global v t rec type paramU

  if {[array names paramU "1,R"] != ""} {
    set v(recSeq)  $paramU(1,R)
  } else {
    set v(recSeq)  0
  }
  set v(listSeq) 1
  set v(listC)   1
  set v(recLab)  [lindex $v(recList)  $v(recSeq)]
  readWavFile
  Redraw all
}

#---------------------------------------------------
# 保存フォルダにあるwavファイルを読み、リストに記憶する
#
proc makeRecListFromDir {{readParam 0} {overWriteRecList 1}} {
  global v t

  set recList {}
  foreach filename [glob -nocomplain [format "%s/*.%s" $v(saveDir) $v(ext)]] {
    set filename [file rootname [file tail $filename]]
    if {$filename == ""} continue
    ;# フォルダおよび拡張子を取り除いたファイル名をリストに格納
    ;# 音名と発声タイプは分けない
    lappend recList $filename
  }
  if $overWriteRecList {
    set v(recList) $recList
    set v(typeList) {""}
  }
  initParamS
  initParamU 0 $recList

  if {$readParam != 0} {
    set act [tk_dialog .confm $t(.confm) \
      $t(makeRecListFromDir,q) question 0 \
      $t(.confm.r) \
      $t(.confm.nr) \
      $t(makeRecListFromDir,a) \
    ]
    set v(paramFile) "$v(saveDir)/oto.ini"
    switch $act {
      0 {readParamFile}
      2 {genParamWizard}
    }
  }
}

#---------------------------------------------------
# 起動時にパラメータ自動推定を行う際のウィザード
#
proc genParamWizard {} {
  global t
  set act [tk_dialog .confm \
    $t(genParamWizard,title) \
    $t(genParamWizard,q) question 3 \
    $t(genParamWizard,a0) \
    $t(genParamWizard,a1) \
    $t(genParamWizard,a2) \
    $t(.confm.c) \
  ]
  switch $act {
    0 {estimateParam}
    1 {autoParamEstimation}
    2 {genParam}
  }
}

#---------------------------------------------------
# 初期化ファイルを指定して読み込む
#
proc readSettings {} {
  global v t startup
 
  set fn [tk_getOpenFile -initialfile $startup(initFile) \
            -title $t(readSettings,title) -defaultextension "tcl" \
            -filetypes { {{tcl file} {.tcl}} {{All Files} {*}} }]
  if {$fn == ""} return

  doReadInitFile $fn
}

#---------------------------------------------------
# メッセージテキストファイルや初期化ファイルを読み込む
#
proc doReadInitFile {initFile} {
  global v f0 power startup dev uttTiming genParam bgmParam estimate t

  if {! [file exists $initFile]} return

  if [catch {open $initFile r} in] { 
    if {[array names t "doReadInitFile,errMsg"] != ""} {
      tk_messageBox -message $t(doReadInitFile,errMsg) \
        -title $t(.confm.fioErr) -icon warning
    } else {
      tk_messageBox -message "can not read the file ($initFile)" \
        -title "File I/O Error" -icon warning
    }
    return
  }

  ;# 簡易サニタイジング
  while {![eof $in]} {
    set l [gets $in]

    if {[regexp {^[ \t]*$} $l]}                   continue  ;# 空行はOK
    if {[regexp {^[ \t]*(;|)[ \t]*#} $l]}         continue  ;# コメント行はOK

    regsub -all -- {\\\[} $l "" l                           ;# \[を消した上で↓のチェックを行う
    if {[regexp {^[ \t]*set[ \t]+[^;\[]+$} $l]} continue    ;# set に;や[がなければOK

    ;# エラーがあった場合
    if {[array names t "doReadInitFile,errMsg2"] != ""} {
      tk_messageBox -message $t(doReadInitFile,errMsg2) \
        -title $t(.confm.fioErr) -icon warning
    } else {
      tk_messageBox -message "Syntax error\nLine: $l\nFile: $initFile\nYou can use only \"set variableName {value}\" in that file." \
        -title "File I/O Error" -icon warning
    }
    close $in
    return
  }
  close $in

  source $initFile
}

#---------------------------------------------------
# reclist.txtを保存する
#
proc saveRecList {} {
  global v t

  set fn [tk_getSaveFile -initialfile $v(recListFile) \
            -title $t(saveRecList,title) -defaultextension "txt" ]
  if {$fn == ""} return

  set v(recListFile) $fn
  if [catch {open $v(recListFile) w} out] { 
    tk_messageBox -message [eval format $t(saveRecList,errMsg)] \
      -title $t(.confm.fioErr) -icon warning
  } else { 
    foreach sn $v(recList) {
      if [catch {set data [puts $out $sn]}] {
        tk_messageBox -message [eval format $t(saveRecList,errMsg2)] \
          -title $t(.confm.fioErr) -icon warning
      }
    }
    close $out
  }
  set v(msg) [eval format $t(saveRecList,doneMsg)]
}

#---------------------------------------------------
# 音名リストファイルを読み、リストに記憶する
#
proc readRecList {args} {
  global v t

  if {[llength $args] == 0 || ! [file exists $v(recListFile)]} {
    set fn [tk_getOpenFile -initialfile $v(recListFile) \
            -title $t(readRecList,title1) -defaultextension "txt" \
            -filetypes { {{reclist file} {.txt}} {{All Files} {*}} }]
  } else {
    set fn [lindex $args 0]
  }
  if {$fn == ""} return
  set v(recListFile) $fn

  if [catch {open $v(recListFile) r} in] { 
    tk_messageBox -message [eval format $t(readRecList,errMsg)] \
      -title $t(.confm.fioErr) -icon warning
  } else { 
    if [catch {set data [read -nonewline $in]}] {
      tk_messageBox -message [eval format $t(readRecList,errMsg2)] \
        -title $t(.confm.fioErr) -icon warning
    }
    regsub -all {[[:space:]]} $data " " data
    set v(recList) {}
    foreach line [split $data " "] {
      if {$line != ""} {
        lappend v(recList) $line
      }
    }
    close $in
  }
  set v(recSeq) 0
  set v(recLab) [lindex $v(recList) $v(recSeq)]
  set v(msg) [eval format $t(readRecList,doneMsg)]
  initParamU
}

#---------------------------------------------------
# 日本語フォントを設定する
#
proc fontSetting {} {
  global v t

  switch $::tcl_platform(platform) {
    unix {
      font create bigkfont -family mincho -size $v(bigFontSize) \
        -weight normal -slant roman
      font create kfont -family mincho -size $v(fontSize) \
        -weight normal -slant roman
      font create smallkfont -family mincho -size $v(smallFontSize) \
        -weight normal -slant roman
    }
    windows {
      font create bigkfont -family $t(fontName) -size $v(bigFontSize) \
        -weight normal -slant roman
      font create kfont    -family $t(fontName) -size $v(fontSize) \
        -weight normal -slant roman
      font create smallkfont -family $t(fontName) -size $v(smallFontSize) \
        -weight normal -slant roman
    }
  }
}

#---------------------------------------------------
# オンラインマニュアル
#
proc onlineHelp {url} {
    global v t

    if {$::tcl_platform(platform) == "windows"} {
        if {[string match $::tcl_platform(os) "Windows NT"]} {
            exec $::env(COMSPEC) /c start $url &
        } {
            exec start $url &
        }
    } else {
        # atode, ここはせめてfirefoxにしないと。。
        if [catch {exec sh -c "netscape -remote 'openURL($url)' -raise"} res] {
            if [string match *netscape* $res] {
                exec sh -c "netscape $url" &
            }
        }
    }
}

#---------------------------------------------------
# D&Dされたものを処理する
#
proc procDnd {objList} {
  global v t

  if $v(paramChanged) {
    set act [tk_dialog .confm $t(.confm) $t(.confm.delParam) \
      question 1 $t(.confm.yes) $t(.confm.no)]
    if {$act == 1} return
  }
  set v(paramChanged) 0

  set obj [lindex $objList 0]
  set obj [file normalize $obj]
  if [file isdirectory $obj] {       ;# フォルダをドロップされた場合
    set v(saveDir) $obj
    makeRecListFromDir ;# 保存フォルダからリストを生成する
    resetDisplay
    setCellSelection
  } elseif [regexp -nocase -- {\.ini$} $obj] {  ;# oto.iniをドロップされた場合
    set v(saveDir) [file dirname $obj]
    makeRecListFromDir 0
    readParamFile $obj
    resetDisplay
    setCellSelection
  } elseif [file isfile $obj] {  ;# ファイルをドロップされた場合
    if [file isdirectory [file dirname $obj]] {
      set v(saveDir) [file dirname $obj]
      makeRecListFromDir ;# 保存フォルダからリストを生成する
      resetDisplay
      setCellSelection
    }
  }
}

#---------------------------------------------------
# 保存ディレクトリを指定する
# 変更したら1、キャンセルしたら0を返す
#
proc choosesaveDir {{readParam 0} {dir ""}} {
  global v t

  if {$dir == ""} {
    ;# 保存ディレクトリを選択する。このとき日本語フォルダがv(saveDir)に指定
    ;# されているとtk_chooseDirectoryの-initialdirが正しく動作しなかったので
    ;# 下記のように一旦cdさせている
    if [file isdirectory $v(saveDir)] {
      set cwd [pwd]
      cd "$v(saveDir)"
      set dir [tk_chooseDirectory -initialdir . -title $t(choosesaveDir,title) -mustexist 1]
      cd "$cwd"
    } else {
      set dir [tk_chooseDirectory -title $t(choosesaveDir,title) -mustexist 1]
    }
  }
  if {$dir != ""} {
    if {[file exists $dir] == 0} {
      tk_messageBox -message "error: can not open $dir" \
        -title $t(.confm.errTitle) -icon error
      return 0    ;# 変更なし
    }
    set v(saveDir) $dir
    updateDirHistory
    set v(msg) [eval format $t(choosesaveDir,doneMsg)]

    if {$readParam != 0} {
      set act [tk_dialog .confm $t(.confm) $t(choosesaveDir,q) \
        question 0 $t(.confm.r) $t(.confm.nr)]
      set v(paramFile) "$v(saveDir)/oto.ini"
      if {$act == 0} readParamFile
    }
    return 1  ;# 変更あり
  }
  return 0    ;# 変更なし
}

#---------------------------------------------------
# ファイルから波形を読む
#
proc readWavFile {} {
  global v snd t

  if {$v(sndLength) > 0} { set v(sndLength) 0 }
  if {[file readable $v(saveDir)/$v(recLab).$v(ext)]} {
    snd read $v(saveDir)/$v(recLab).$v(ext)
    if {[snd cget -channels] != 1} {
      set snd [snd convert -channels Mono]
    }
    set v(sndLength) [snd length -unit SECONDS]
    if {$v(sndLength) <= 0} {
      set v(wavepps) 1
    } elseif $v(showWhole) {
      set v(wavepps) [expr $v(cWidth) / $v(sndLength)]  ;# wav全体を表示
    } elseif {[expr int($v(sndLength) * $v(wavepps))] > 48000} {
      set v(wavepps) [expr 48000 / $v(sndLength)]
    }
  }
}

#---------------------------------------------------
# 平均律の各音階の周波数を求める
#
proc setSinScale {} {
  global v t
  set v(sinScale) {}
  set v(sinNote) {}
  for {set oct $v(sinScaleMin)} {$oct <= $v(sinScaleMax)} {incr oct} {
    for {set i 0} {$i < 12} {incr i} {
      lappend v(sinScale) [expr int(27.5 * pow(2, $oct + ($i - 9.0)/12.0) + 0.5)]
      lappend v(sinNote) "[lindex $v(toneList) $i]$oct"
    }
  }
}

#---------------------------------------------------
# F0計算中にキーボード、マウス入力を制限させるための窓
# うまく窓を表示できていないが、F0計算中の入力を制限できるので
# とりあえずOK(F0計算中に入力すると落ちることがあるため)
#
proc waitWindow {message fraction} {
  global t
  set w .waitw
  if {$fraction >= 1.0 && [winfo exists $w]} {
    grab release $w
    destroy $w
    return
  }
  if [winfo exists $w] return

  toplevel $w
  grab set $w
  wm title $w $t(waitWindow,title)
  label $w.l -text "calc.."
  pack $w.l
  wm transient $w .
  wm geom $w +100+100
}

#---------------------------------------------------
# 既存の右ブランク値の正負を書き換えて統一させる
#
proc changeE {} {
  global v paramS paramU paramUsize t

  ;# 既存の値があるかチェック
  set exist 0
  for {set i 1} {$i < $paramUsize} {incr i} {
    if {[array names paramU "$i,5"] != ""} {
      set exist 1
      break
    }
  }
  if {$exist == 0} return   ;# 既存の値がなければ終了

  ;# 正の値にする場合の処理
  if {$v(setE) >= 0} {
    ;# 負の値がないかチェック
    set exist 0
    for {set st 1} {$st < $paramUsize} {incr st} {
      if {[array names paramU "$st,5"] != "" && $paramU($st,5) < 0} {
        set exist 1
        break
      }
    }
    if {$exist == 0} return   ;# 既存の値がなければ終了

    set act [tk_dialog .confm $t(changeE,title) $t(changeE,q) \
      question 0 $t(changeE,a) $t(changeE,a2) $t(changeE,a3)]
    switch $act {
      0 {
          snack::sound sWork -channels Mono -rate $v(sampleRate)
          for {set i $st} {$i < $paramUsize} {incr i} {
            if {[array names paramU "$i,5"] != "" && $paramU($i,5) < 0} {
              set fname $v(saveDir)/$paramU($i,0).$v(ext)
              sWork read $fname
              set newE [cut3 [expr [sWork length -unit SECONDS] * 1000 - $paramS($i,E) * 1000]] 
              changeParam paramU($i,5) $newE $i 5
            }
          }
        }
      1 {
          for {set i $st} {$i < $paramUsize} {incr i} {
            if {[array names paramU "$i,5"] != "" && $paramU($i,5) < 0} {
              changeParam paramU($i,5) [expr - $paramU($i,5)]  $i 5
            }
          }
        }
    }

  } else {
    ;# 正の値がないかチェック
    set exist 0
    for {set st 1} {$st < $paramUsize} {incr st} {
      if {[array names paramU "$st,5"] != "" && $paramU($st,5) > 0} {
        set exist 1
        break
      }
    }
    if {$exist == 0} return   ;# 既存の値がなければ終了

    set act [tk_dialog .confm $t(changeE,title) \
      $t(changeE,q2) \
      question 0 $t(changeE,a21) $t(changeE,a22) $t(changeE,a3)]
    switch $act {
      0 {
          snack::sound sWork -channels Mono -rate $v(sampleRate)
          for {set i $st} {$i < $paramUsize} {incr i} {
            if {[array names paramU "$i,5"] != "" && $paramU($i,5) > 0} {
              if {[array names paramS "$i,S"] != ""} {
                set S $paramS($i,S)
              } else {
                set S 0
              }
              set newE [cut3 [expr - ($paramS($i,E) - $S) * 1000] ]
              changeParam paramU($i,5) $newE $i 5
            }
          }
        }
      1 {
          for {set i $st} {$i < $paramUsize} {incr i} {
            if {[array names paramU "$i,5"] != "" && $paramU($i,5) > 0} {
              changeParam paramU($i,5) [expr - $paramU($i,5)]  $i 5
            }
          }
        }
    }
  }
  Redraw all
}

#---------------------------------------------------
#
proc changeTone {chg} {
  global f0 v t
  
  set next [expr [lsearch $v(toneList) $f0(guideTone)] + $chg]

  if {$next < 0} {
    if {$f0(guideOctave) > $v(sinScaleMin)} {
      set next [expr ($next + [llength $v(toneList)])]
      incr f0(guideOctave) -1
    } else {
      set next [lsearch $v(toneList) $f0(guideTone)]
    }
  } elseif {$next >= [llength $v(toneList)]} {
    if {$f0(guideOctave) < $v(sinScaleMax)} {
      set next [expr $next % [llength $v(toneList)]]
      incr f0(guideOctave)
    } else {
      set next [lsearch $v(toneList) $f0(guideTone)]
    }
  }

  set f0(guideTone) [lindex $v(toneList) $next]
  if {$v(playOnsaStatus)} {  ;# もしループ再生中だったなら、
    toggleOnsaPlay           ;# 停止して、
    toggleOnsaPlay           ;# もう一度再生。
  }
}

#---------------------------------------------------
# 連続音wavのoto.iniを読み、
# 足りないエイリアスを単独音のパラメータをコピーして生成する
#
proc aliasComplement {} {
  global v paramU paramUsize t

  ;# set fnames [glob -nocomplain [format "%s/oto.ini" $v(saveDir)]]

  set act [tk_dialog .confm $t(.confm) \
    $t(aliasComplement,q) \
    question 0 $t(.confm.yes) $t(.confm.no) $t(.confm.c)]
  if {$act == 2} return

  set aliasList {}

  if {$act == 0} {
    ;# 併用する連続音oto.ini(原音設定済みのもの)を選ぶ
    set fn [tk_getOpenFile -initialfile $v(paramFile) \
            -title $t(aliasComplement,selMsg) -defaultextension "ini" \
            -filetypes { {{UTAU param file} {.ini}} {{All Files} {*}} }]
    if {$fn == ""} return
 
    ;# 併用する連続oto.iniを開く
    if [catch {open $fn r} fp] { 
      tk_messageBox -message "error: can not open $fn" \
        -title $t(.confm.fioErr) -icon warning
      return
    }

    ;# 連続oto.iniからエイリアスを呼んでリストに登録する
    while {![eof $fp]} {
      set l [gets $fp]   ;# "fname=A,S,C,E,P,O"
      set p [split $l "=,"]
      if {[llength $p] == 7 && [lindex $p 1] != ""} {
        lappend aliasList "[lindex $p 1]"
      }
    }
    close $fp
  }

  initProgressWindow

  ;# 編集中のパラメータ(単独音のみ又は単独連続混在)から登録済みのエイリアスリストを作る
  for {set r 1} {$r < $paramUsize} {incr r} {
    if {[array names paramU "$r,6"] != "" && [regexp { } $paramU($r,6)]} {
      if {[lsearch $aliasList "$paramU($r,6)"] < 0} {
        lappend aliasList "$paramU($r,6)"
      }
    }
    updateProgressWindow [expr 30 * $r / $paramUsize]
  }

  ;# 現在開いている単独音一覧から生成できるエイリアスで、かつ
  ;# 既存のエイリアスリストに存在しないものを見つけてエイリアスを追加登録する
  set prevList {a i u e o n -}
  set addNum 0
  set r 1
  while {$r < $paramUsize} {
    if {! [regexp {^_} $paramU($r,0)]} {
      foreach prev $prevList {
        set alias "$prev $paramU($r,0)"
        if {[lsearch $aliasList $alias] < 0} {
          duplicateEntp .entpwindow.t $r       ;# パラメータを複製する
          set paramU([expr $r + 1],6) $alias   ;# エイリアスを設定する
          lappend aliasList $alias
          incr addNum
          incr r
        }
      }
    }
    incr r
    updateProgressWindow [expr 30 + 70 * $r / $paramUsize]
  }
  deleteProgressWindow
  tk_messageBox -message "[eval format $t(aliasComplement,doneMsg)] (num=$addNum)" -title $t(aliasComplement,doneTitle) -icon info
}

#---------------------------------------------------
# 「を」や「a を」などが無い場合「お」を複製して補完する
# 「ぢ」「づ」にも対応
#
proc woComplement {} {
  global v paramU paramUsize t
  set addNum 0
  set addNum [expr $addNum + [_woComplement お を]]   ;# 「を」を「お」で補完する
  set addNum [expr $addNum + [_woComplement じ ぢ]]   ;# 「ぢ」を「じ」で補完する
  set addNum [expr $addNum + [_woComplement ず づ]]   ;# 「づ」を「ず」で補完する
  tk_messageBox -message "[eval format $t(woComplement,doneMsg)] (num=$addNum)" -title $t(woComplement,doneTitle) -icon info
}

#---------------------------------------------------
# 文字dst("を"など) が無いときに文字src("お"など)を複製して補完する
#
proc _woComplement {src dst} {
  global v paramU paramUsize t

  ;# どんな「を」(dst)のデータがあるかを探してwoListに保存
  set woList {}
  for {set r [expr $paramUsize - 1]} {$r >= 0} {incr r -1} {
    if {[array names paramU "$r,6"] != "" && [regexp "^. $dst" $paramU($r,6)]} {
      lappend woList $paramU($r,6)
    } elseif [regexp "^$dst" $paramU($r,0)] {
      lappend woList $paramU($r,0)
    }
  }

  ;# 「* お」(src)のデータが存在し「* を」(dst)のデータが無ければ「* お」を複製して「* を」を作る
  set addNum 0
  for {set r [expr $paramUsize - 1]} {$r >= 0} {incr r -1} {
    if {[array names paramU "$r,6"] != "" && [regexp $src $paramU($r,6)]} {
      regsub -- $src $paramU($r,6) $dst alias
      if {[lsearch $woList $alias] < 0} {
        duplicateEntp .entpwindow.t $r       ;# パラメータを複製する
        set paramU([expr $r + 1],6) $alias   ;# エイリアスを設定する
        incr addNum
        lappend woList $alias
      }
    }
    if [regexp "^$src" $paramU($r,0)] {
      regsub -- $src $paramU($r,0) $dst alias
      if {[lsearch $woList $alias] < 0} {
        duplicateEntp .entpwindow.t $r       ;# パラメータを複製する
        set paramU([expr $r + 1],6) $alias   ;# エイリアスを設定する
        incr addNum
        lappend woList $alias
      }
    }
  }
  return $addNum
}

#---------------------------------------------------
# 保存フォルダ内の全wavファイル名冒頭に_を追記する
#
proc addUnderScore {} {
  global v paramU paramUsize t

  ;# 実行確認
  set act [tk_dialog .confm $t(.confm) \
    $t(addUnderScore,q) \
    question 1 $t(.confm.yes) $t(.confm.no)]
  if {$act == 1} return

  ;# 処理対象のファイルを得る
  set fnames [glob -nocomplain [format "%s/*.%s" $v(saveDir) $v(ext)]]
  set procNum [llength $fnames]
  set i 0
  set N 0
  initProgressWindow
  foreach fullpath $fnames {
    set filename [file tail $fullpath]
    if {![regexp {^_} $filename] && $filename != ".$v(ext)"} {
      set dir [file dirname $fullpath]
      file rename "$dir/$filename" "$dir/_$filename"                    ;# ファイル名変更
      incr N
    }
    updateProgressWindow [expr 100 * $i / $procNum]
    incr i
  }

  for {set i 0} {$i < [llength $v(recList)]} {incr i} {
    set fid [lindex $v(recList) $i]
    if ![regexp {^_} $fid] {
      set v(recList) [lreplace $v(recList) $i $i "_$fid"]  ;# recList変更
    }
  }

  for {set i 1} {$i < $paramUsize} {incr i} {
    if ![regexp {^_} $paramU($i,0)] {
      set paramU($i,0) "_$paramU($i,0)"                       ;# paramU変更
    }
  }

  ;# 終了通知
  deleteProgressWindow
  if {$N > 0} {
    tk_messageBox -message "[eval format $t(addUnderScore,doneMsg)] (num=$N)" -title $t(addUnderScore,doneTitle) -icon info
  } else {
    tk_messageBox -message "[eval format $t(addUnderScore,doneMsg2)] (num=$N)" -title $t(addUnderScore,doneTitle) -icon info
  }

  ;# 波形再読み込み、再描画
  resetDisplay
  setCellSelection
}

#---------------------------------------------------
# 保存フォルダ内の全wavファイルをモノラルに変換する
#
proc convertMonoAll {} {
  global v t paramUsize

  ;# 実行確認
  set act [tk_dialog .confm $t(.confm) $t(convertMonoAll,q) \
    question 1 $t(.confm.yes) $t(.confm.no)]
  if {$act == 1} return

  snack::sound sWork

  ;# モノラル化
  initProgressWindow
  set fnames [glob -nocomplain [format "%s/*.%s" $v(saveDir) $v(ext)]]
  set N [llength $fnames]
  set i 0
  foreach filename $fnames {
    sWork read $filename
    if {[sWork cget -channels] > 1} {
      set sWork [sWork convert -channels Mono]
      sWork write $filename
    }
    updateProgressWindow [expr 100 * $i / $N]
    set v(msg) "($i / $N)"
    incr i
  }

  ;# 終了通知
  deleteProgressWindow
  tk_messageBox -message [eval format $t(convertMonoAll,doneMsg)] -title $t(convertMonoAll,doneTitle) -icon info

  ;# 波形再読み込み、再描画
  readWavFile
  Redraw all
}

#---------------------------------------------------
# 保存フォルダ内の全wavファイルのDC成分を一括除去する
#
proc removeDCall {} {
  global v t paramUsize

  ;# 実行確認
  set act [tk_dialog .confm $t(.confm) $t(removeDCall,q) \
    question 1 $t(.confm.yes) $t(.confm.no)]
  if {$act == 1} return

  ;# フィルタ作成
  set flt [snack::filter iir -numerator "0.99 -0.99" -denominator "1 -0.99"] 
  snack::sound sWork

  ;# DC成分除去
  initProgressWindow
  set fnames [glob -nocomplain [format "%s/*.%s" $v(saveDir) $v(ext)]]
  set N [llength $fnames]
  set i 0
  foreach filename $fnames {
    sWork read $filename
    sWork filter $flt -continuedrain 0
    sWork write $filename

    updateProgressWindow [expr 100 * $i / $N]
    set v(msg) "($i / $N)"
    incr i
  }

  ;# 終了通知
  deleteProgressWindow
  tk_messageBox -message [eval format $t(removeDCall,doneMsg)] -title $t(removeDCall,doneTitle) -icon info

  ;# 波形再読み込み、再描画
  readWavFile
  Redraw all
}

#---------------------------------------------------
# 指定した範囲のデータに対して一律に値を設定したり加減算したりする
#
proc changeCell {} {
  global chaWindow paramU v t

  if [isExist $chaWindow] return ;# 二重起動を防止
  toplevel $chaWindow
  wm title $chaWindow $t(changeCell,title)
  bind $chaWindow <Escape> "destroy $chaWindow"

  set ccMode 1
  set ccVal  0
  set restrictMode 1
  set rVal 0

  set f [frame $chaWindow.f -padx 5 -pady 5]
  pack $f

  radiobutton $f.r1 -variable ccMode -value  1 -text $t(changeCell,r1)
  radiobutton $f.r2 -variable ccMode -value -1 -text $t(changeCell,r2)
  radiobutton $f.r3 -variable ccMode -value  0 -text $t(changeCell,r3)
  radiobutton $f.r4 -variable ccMode -value  2 -text $t(changeCell,r4)
  grid $f.r1 $f.r2 $f.r3 $f.r4
  $f.r1 select

  set f1 [frame $chaWindow.f1 -padx 5 -pady 5]
  pack $f1
  label $f1.l -text $t(changeCell,l)
  entry $f1.e -textvar ccVal 
  grid  $f1.l $f1.e
  focus $f1.e

  set f2 [frame $chaWindow.f2 -padx 5 -pady 5]
  pack $f2
  label $f2.l -text $t(changeCell,rtitle)
  grid $f2.l

  set f3 [frame $chaWindow.f3 -padx 5 -pady 5]
  pack $f3
  radiobutton $f3.r1 -variable restrictMode -value 1 -text $t(changeCell,rr1)
  radiobutton $f3.r2 -variable restrictMode -value 2 -text $t(changeCell,rr2)
  radiobutton $f3.r3 -variable restrictMode -value 3 -text $t(changeCell,rr3)
  radiobutton $f3.r4 -variable restrictMode -value 4 -text $t(changeCell,rr4)
  grid $f3.r1 $f3.r2 $f3.r3 $f3.r4
  $f3.r1 select

  set f4 [frame $chaWindow.f4 -padx 5 -pady 5]
  pack $f4
  label $f4.l -text $t(changeCell,rl)
  entry $f4.e -textvar rVal 
  grid  $f4.l $f4.e

  set f5 [frame $chaWindow.f5 -padx 5 -pady 5]
  pack $f5
  button $f5.r -text $t(.confm.run) -command {
    doChangeCell $ccMode $ccVal $restrictMode $rVal
    destroy $chaWindow
  }
  button $f5.cancel -text $t(.confm.c) -command {destroy $chaWindow}
  grid $f5.r $f5.cancel
}

#---------------------------------------------------
# 指定した範囲のデータに対して一律に値を設定したり加減算したりする
#
proc doChangeCell {mode val rMode rVal} {
  global paramU v
  foreach pos [.entpwindow.t curselection] {
    ;# pasteする位置と現在の値を得る
    set u [split $pos ","]
    set r [lindex $u 0]
    set c [lindex $u 1]
    if {$c == 6} continue   ;# エイリアスの欄はskip
    if {[array names paramU $pos] != ""} {
      set curVal $paramU($pos)
    } else {
      set curVal 0
    }
    switch $rMode {
      2 { if {$curVal != $rVal} continue }  ;# 一致モードで一致でなかったら
      3 { if {$curVal <  $rVal} continue }  ;# 以上モードで以上でなかったら
      4 { if {$curVal >  $rVal} continue }  ;# 以下モードで以下でなかったら
    }
    switch $mode {
       0 { set paramU($pos) $val }
       1 { set paramU($pos) [expr $curVal + $val] }
      -1 { set paramU($pos) [expr $curVal - $val] }
       2 { set paramU($pos) [expr int($curVal)] }
    }
    paramU2paramS $r
  }
  set v(paramChanged) 1
  Redraw all
  setEPWTitle
}

#---------------------------------------------------
# エイリアス一括変換
#
proc changeAlias {} {
  global traWindow t

  if [isExist $traWindow] return ;# 二重起動を防止
  toplevel $traWindow
  wm title $traWindow $t(changeAlias,title)
  bind $traWindow <Escape> "destroy $traWindow"

  set delPreNum  0
  set delPostNum 0
  set aliasMax   0     ;# 重複番号の最大値

  set f [frame $traWindow.f -padx 5 -pady 5]
  pack $f
  set r 0

  label  $f.l$r -text $t(changeAlias,trans)
  entry  $f.e$r -textvar transRule
  grid $f.l$r -row $r -column 0 -sticky e  -pady 2
  grid $f.e$r -row $r -column 1 -sticky we -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,delPreNum)
  entry  $f.e$r -textvar delPreNum
  grid $f.l$r -row $r -column 0 -sticky e  -pady 2
  grid $f.e$r -row $r -column 1 -sticky we -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,delPostNum)
  entry  $f.e$r -textvar delPostNum
  grid $f.l$r -row $r -column 0 -sticky e  -pady 2
  grid $f.e$r -row $r -column 1 -sticky we -pady 2
  incr r

  # エイリアス重複に関する設定欄
  label $f.ll$r   -text $t(changeAlias,aliasMaxNum)
  entry $f.e$r    -textvar aliasMax -wi 10
  grid $f.ll$r -row $r -column 0 -sticky e
  grid $f.e$r  -row $r -column 1 -sticky we
  incr r

  # 実行、キャンセルボタン
  button $f.b$r -text $t(.confm.run) -command {
    doTransAlias $transRule $delPreNum $delPostNum $aliasMax
    destroy $traWindow
  }
  button $f.bc$r -text $t(.confm.c) -command {
    destroy $traWindow
  }
  grid $f.b$r  -row $r -column 0 -sticky e  -pady 2
  grid $f.bc$r -row $r -column 1 -sticky w  -pady 2
  incr r

  # ヘルプ説明
  label  $f.l$r -text $t(changeAlias,tips0)
  grid $f.l$r -row $r -column 0 -columnspan 2 -sticky w -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,tips0b)
  grid $f.l$r -row $r -column 0 -columnspan 2 -sticky w -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,tips1)
  grid $f.l$r -row $r -column 0 -columnspan 2 -sticky w -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,tips2)
  grid $f.l$r -row $r -column 0 -columnspan 2 -sticky w -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,tips3)
  grid $f.l$r -row $r -column 0 -columnspan 2 -sticky w -pady 2
  incr r

  label  $f.l$r -text $t(changeAlias,ex1)
  grid $f.l$r -row $r -column 0 -columnspan 2 -sticky w -pady 2
  incr r

  raise $f.e0
  focus $f.e0
}

#---------------------------------------------------
# 文字列（a あ2_A3など）の冒頭から
# 「あ」や「a あ」などの音名とその後の部分とに分解してリストで返す
#
proc parseOtomei {str} {
  set otomei ""           ;# 音名を初期化
  set suffix ""           ;# 接尾辞を初期化
  regexp {^([a-zA-Z\-] )(.+)$} $str dummy otomei str  ;# 連続音名の場合、先行母音部を取り出してotomeiへ。
  ;# otomeiを完成させる
  for {set i 0} {$i < [string length $str]} {incr i} {
    set char [string range $str $i $i]
    if {$char != "_" && [isKana $char]} {
      set otomei "$otomei$char"
    } else {
      break
    }
  }
  ;# suffixを作る
  if {$i < [string length $str]} {
    set suffix [string range $str $i end]
  }
  return [list $otomei $suffix]
}

#---------------------------------------------------
# エイリアス一括変換
#
proc doTransAlias {{transRule ""} {delPreNum 0} {delPostNum 0} {aliasMax 0}} {
  global paramU paramUsize v

  if {$transRule == ""} return

  initProgressWindow

  ;# ファイル名、エイリアスを挿入
  for {set r 1} {$r < $paramUsize} {incr r} {
    set newAlias $transRule

    ;# %m、%sの処理（%mに音名を挿入、%sに現在のエイリアスの接尾辞）
    if {[regexp {%[ms]} $newAlias]} {
      if {[array names paramU "$r,6"] != "" && $paramU($r,6) != ""} {
        set ret [parseOtomei $paramU($r,6)]
        set otomei [lindex $ret 0]
        set suffix [lindex $ret 1]
      } else {
        set ret [parseOtomei $paramU($r,0)]
        set otomei [lindex $ret 0]
        set suffix [lindex $ret 1]
      }
      set newAlias [string map [eval format "{%%m \"$otomei\"}"] $newAlias]
      set newAlias [string map [eval format "{%%s \"$suffix\"}"] $newAlias]
    }

    ;# %fの処理（%fにファイル名を挿入）
    if {[array names paramU "$r,0"] != "" && $paramU($r,0) != ""} {
      set fid   $paramU($r,0)
      set newAlias [string map [eval format "{%%f \"$fid\"}"] $newAlias]
    } else {
      set newAlias [string map {%f ""} $newAlias]
    }
    ;# %aの処理（%aに現在のエイリアスを挿入）
    if {[array names paramU "$r,6"] != "" && $paramU($r,6) != ""} {
      set alias $paramU($r,6)
      ;# 冒頭から指定した文字数を削除
      if {$delPreNum > 0} {
        set alias [string replace $alias 0 [expr $delPreNum -1]]
      }
      ;# 末尾から指定した文字数を削除
      if {$delPostNum > 0} {
        set st [expr [string length $alias] - $delPostNum]
        set alias [string replace $alias $st end]
      }
      if {$alias != ""} {
        set newAlias [string map [eval format "{%%a \"$alias\"}"] $newAlias]
      } else {
        set newAlias [string map {%a ""} $newAlias]
      }
    } else {
      set newAlias [string map {%a ""} $newAlias]
    }

    set paramU($r,6) $newAlias
    if {[regexp {%r} $paramU(1,6)] } {
      updateProgressWindow [expr  80 * $r / $paramUsize]
    } else {
      updateProgressWindow [expr 100 * $r / $paramUsize]
    }
  }

  ;# 重複番号を挿入
  if {[regexp {%r} $paramU(1,6)] } {
    ;# もし%rがあれば、各音の重複番号を求める
    array unset cnum   ;# 音の名前ごとの重複回数を入れる配列を初期化
    array unset rseq   ;# 音ごとの重複回数を入れる配列を初期化
    for {set r 1} {$r < $paramUsize} {incr r} {
      if {[array names paramU "$r,6"] != "" && $paramU($r,6) != ""} {
        set src $paramU($r,6)
      } else {
        set src $paramU($r,0)
      }

      if {[array names cnum "$src"] != ""} {
        incr cnum($src)
      } else {
        set cnum($src) 1
      }
      set rseq($r) $cnum($src)
      updateProgressWindow [expr 80 + 10 * $r / $paramUsize]
    }
    ;# 音ごとの重複回数をエイリアスに書き込む
    set deleteList {}
    for {set r 1} {$r < $paramUsize} {incr r} {
      if {$rseq($r) <= 1} {
        ;# 重複がないとき
        if {[array names paramU "$r,6"] != "" && $paramU($r,6) != ""} {
          set paramU($r,6) [string map {%r ""} $paramU($r,6)]   ;# %rを消す
        }
      } else {
        ;# 重複があるとき
        set seq $rseq($r)
        if {$aliasMax <= 0 || $seq <= $aliasMax} {
          set paramU($r,6) [string map "%r $seq" $paramU($r,6)]   ;# 重複番号を付ける
        } else {
          lappend deleteList $r
        }
      }
      updateProgressWindow [expr 90 + 10 * $r / $paramUsize]
    }
    ;# 上限を超えたエントリを削除する
    foreach r [lreverse $deleteList] {
      deleteEntp dummy $r
    }
  }

  ;# 波形再読み込み、再描画
  deleteProgressWindow
  set v(paramChanged) 1
  resetDisplay
  setCellSelection
  setEPWTitle
}

#---------------------------------------------------
# 保存フォルダの各wavの両端を指定秒カット
#
proc cutWav {} {
  global cutWindow v t

  if [isExist $cutWindow] return ;# 二重起動を防止
  toplevel $cutWindow
  wm title $cutWindow $t(cutWav,title)
  bind $cutWindow <Escape> "destroy $cutWindow"

  set L 0
  set R 0

  set r 0
  label $cutWindow.l0$r -text $t(cutWav,L)
  entry $cutWindow.l1$r -textvar L
  label $cutWindow.l2$r -text $t(cutWav,sec)
  grid  $cutWindow.l0$r -row $r -column 0 -sticky e
  grid  $cutWindow.l1$r -row $r -column 1 -sticky we
  grid  $cutWindow.l2$r -row $r -column 2 -sticky w
  incr r

  label $cutWindow.l0$r -text $t(cutWav,R)
  entry $cutWindow.l1$r -textvar R
  label $cutWindow.l2$r -text $t(cutWav,sec)
  grid  $cutWindow.l0$r -row $r -column 0 -sticky e
  grid  $cutWindow.l1$r -row $r -column 1 -sticky we
  grid  $cutWindow.l2$r -row $r -column 2 -sticky w
  incr r

  checkbutton $cutWindow.l0$r -variable v(adjSE) -text $t(cutWav,adjSE)
  grid  $cutWindow.l0$r    -row $r -column 0 -columnspan 3
  incr r

  frame $cutWindow.f$r
  button $cutWindow.f$r.l0 -text $t(.confm.run) -command {
    destroy $cutWindow
    doCutWav $L $R
  }
  button $cutWindow.f$r.l1 -text $t(.confm.c)   -command {
    destroy $cutWindow
  }
  pack $cutWindow.f$r.l0 $cutWindow.f$r.l1 -side left
  grid  $cutWindow.f$r    -row $r -column 0 -columnspan 3
  incr r
}

#---------------------------------------------------
# 保存フォルダの各wavの両端を指定秒カット
#
proc doCutWav {L R} {
  global paramS paramU paramUsize v t

  if {$L == ""} { set L 0 }
  if {$R == ""} { set R 0 }

  if {$L < 0 || $R < 0} {
    tk_messageBox -message [eval format $t(doCutWav,errMsg)] \
      -title $t(.confm.errTitle) -icon error
    return
  }

  ;# 両端カット
  snack::sound sWork
  initProgressWindow
  set cutS [expr int($L * $v(sampleRate))]
  array unset cutlog   ;# カット済みのfidを登録する配列
  ;# v(setS)のバックアップをとり、一時的にv(setS)=0にする
  ;# (↑changeParamでSの値のみ変更させるため)
  set setSbackup $v(setS)
  set v(setS) 0
  for {set r 1} {$r < $paramUsize} {incr r} {
    set fid $paramU($r,0)

    ;# カット済みファイルなら次へ
    if {[array names cutlog $fid] != ""} continue
    set cutlog($fid) 1

    ;# wavファイルを読む
    set filename $v(saveDir)/$fid.$v(ext)
    sWork read $filename
    set len [sWork length -unit SAMPLES]
    set cutE [expr $len - int($R * $v(sampleRate))]
    sWork crop $cutS $cutE
    sWork write $filename

    ;# S,E値を修正
    if $v(adjSE) {
      ;# S値を補正
      set c 1
      if {[array names paramU "$r,$c"] != ""} {
        set oldValue $paramU($r,$c)
        set newValue [cut3 [expr $oldValue - $L * 1000]]
        if {$newValue < 0} { set newValue 0 }
        changeParam $oldValue $newValue $r $c
      }

      ;# E値を補正
      set c 5
      if {$R > 0 && [array names paramU "$r,$c"] != "" && $v(setE) > 0} {
        set oldValue $paramU($r,$c)
        set newValue [cut3 [expr $oldValue  - $R * 1000]]
        if {$newValue < 0} { set newValue 0 }
        changeParam $oldValue $newValue $r $c
      }

      paramU2paramS $r
    }
    updateProgressWindow [expr 100 * $r / $paramUsize]
    set v(msg) "($r / $paramUsize)"
  }

  ;# v(setS)を以前の値に戻す
  set v(setS) $setSbackup

  ;# 波形再読み込み、再描画
  set v(paramChanged) 1
  readWavFile
  Redraw all

  ;# 終了通知
  deleteProgressWindow
  tk_messageBox -message [eval format $t(doCutWav,doneMsg)] \
    -title $t(doCutWav,doneTitle) -icon info
}

#---------------------------------------------------
# 各パラメータを右上がりゼロクロス位置に移動する際の設定窓
#
proc zeroCross {} {
  global zcwindow v t

  if [isExist $zcwindow] return  ;# 二重起動を防止
  toplevel $zcwindow
  wm title $zcwindow $t(zeroCross,title)
  wm resizable $zcwindow 0 0
  bind $zcwindow <Escape> "destroy $zcwindow"

  set w [frame $zcwindow.al]
  pack $w
  set row 0

  ;# 対象の設定
  # frame $w.fem
  label $w.l$row -text $t(zeroCross,target)
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable pZeroCross(S)
  label $w.fe$row.t -text $t(zeroCross,S)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.l$row  -sticky w -row $row -column 0
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable pZeroCross(C)
  label $w.fe$row.t -text $t(zeroCross,C)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable pZeroCross(E)
  label $w.fe$row.t -text $t(zeroCross,E)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable pZeroCross(P)
  label $w.fe$row.t -text $t(zeroCross,P)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable pZeroCross(O)
  label $w.fe$row.t -text $t(zeroCross,O)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# ボタンの設定
  button $w.do -text $t(zeroCross,runAll) -command {
    if {$::tcl_platform(os) != "Darwin"} { grab set $zcwindow }
    doZeroCross all
    if {$::tcl_platform(os) == "Darwin"} { deleteProgressWindow } ;# なぜかmacではdoZeroCrossの中で消せなかった(?)
    if {$::tcl_platform(os) != "Darwin"} { grab release $zcwindow }
    destroy $zcwindow
  }
  grid $w.do     -sticky we -row $row -column 0
  button $w.do2 -text $t(zeroCross,runSel) -command {
    if {$::tcl_platform(os) != "Darwin"} { grab set $zcwindow }
    doZeroCross sel
    if {$::tcl_platform(os) == "Darwin"} { deleteProgressWindow } ;# なぜかmacではdoZeroCrossの中で消せなかった(?)
    if {$::tcl_platform(os) != "Darwin"} { grab release $zcwindow }
    #destroy $zcwindow
  }
  grid $w.do2    -sticky we -row $row -column 1
  button $w.cancel -text $t(.confm.c) -command {destroy $zcwindow}
  grid $w.cancel -sticky we -row $row -column 2
  incr row
}

#---------------------------------------------------
# パラメータ位置が右上がりゼロクロス位置に来るよう補正する
#
proc doZeroCross {{mode all}} {
  global paramS paramU paramUsize pZeroCross v t

  ;# 推定する行番号のリストを作る
  set targetList {}
  if {$mode == "all"} {
    for {set i 1} {$i < $paramUsize} {incr i} {
      lappend targetList $i
    }
  } else {
    foreach pos [.entpwindow.t curselection] {
      set r [lindex [split $pos ","] 0]
      if {[lindex $targetList end] != $r} {
        lappend targetList $r
      }
    }
  }

  initProgressWindow

  set seq 0
  foreach r $targetList {
    set fid $paramU($r,0)
    snack::sound sWork
    sWork read "$v(saveDir)/$fid.$v(ext)"
    array unset wt
    set change 0

    foreach kind {S C E P O} {
      if {! $pZeroCross($kind)} continue
      if {[array get paramS "$r,$kind"] == ""} continue

      set orgParamS $paramS($r,$kind)
      set val [expr int($v(sampleRate) * $paramS($r,$kind))]

      set halfRange [expr int(0.06 * $v(sampleRate) / 2)]
      set st [expr $val - $halfRange]
      if {$st < 0} { set st 0 }
      set en [expr $val + $halfRange]
      if {$en > [sWork length]} {set en [sWork length]}

      if {[catch {set seriestmp [sWork pitch -method ESPS -start $st -end $en \
          -framelength 0.01 -windowlength 0.01 -maxpitch 800]} ret]} {
        if {$ret != ""} {
          puts "error: $ret"
        }
        set seriestmp {}
      }
      if {[llength $seriestmp] > 0} {
        set f0now [lindex [split [lindex $seriestmp 0] " "] 0]
        if {$f0now > 0} {
          set halfRange [expr int(1.5 / $f0now * $v(sampleRate) / 2)]
          set st [expr $val - $halfRange]
          if {$st < 0} { set st 0 }
          set en [expr $val + $halfRange]
          if {$en > [sWork length]} { set en [sWork length]}
        }
      }

      set max   [sWork sample $st]     ;# 瞬時値の最大値を入れる
      set zc     $st                   ;# ゼロクロス位置を入れていく
      set max_zc $st                   ;# 瞬時値最大の左にあるゼロクロスを入れる
      for {set i $st} {$i < $en} {incr i} {
        set wnow [lindex [sWork sample $i] 0]
        set wt([expr $i - $st]) $wnow
        if {$i > $st && $wt([expr $i - $st - 1]) <= 0 && 0 < $wnow} {
          set zc [expr $i - 1]
        }
        if {$max < $wnow} {
          set max   $wnow
          set max_zc $zc
        }
      }

      set newParamS [expr double($max_zc) / $v(sampleRate)]
      if {$newParamS != $orgParamS} {
        set paramS($r,$kind) $newParamS
        set change 1
      }
    }
    if {$change} { paramS2paramU $r }
    updateProgressWindow [expr 100 * $seq / [llength $targetList]]
    incr seq
  }

  deleteProgressWindow

  ;# 再描画
  set v(paramChanged) 1
  Redraw all
}

#---------------------------------------------------
# wavの左右ブランクから外側をカット。それに合わせてパラメータ値を変更する
#
proc cutBlank {w} {
  global paramS paramU v t

  if {$v(cutBlank) == 0} return

  ;# 試聴対象の行番号リストを作成
  set wavList {}
  foreach pos [$w curselection] {
    set r [lindex [split $pos ","] 0]
    if {[lindex $wavList end] != $r} {
      lappend wavList $r
    }
  }
  if {[llength $wavList] <= 0} {
    lappend wavList $v(listSeq)
  }

  ;# v(setS)のバックアップをとり、一時的にv(setS)=0にする
  ;# (↑changeParamでSの値のみ変更させるため)
  set setSbackup $v(setS)
  set v(setS) 0

  foreach r $wavList {
    set fid $paramU($r,0)
    set S  0
    set E -1
    set Eu 0
    if {[array get paramS "$r,S"] != ""} {
      set S [expr int($v(sampleRate) * $paramS($r,S))]
    }
    if {[array get paramS "$r,E"] != ""} {
      set E [expr int($v(sampleRate) * $paramS($r,E))]
    }
    if {[array get paramU "$r,5"] != ""} {
      set Eu $paramU($r,5))]
    }
    ;# 両端カット
    if {$S != 0 || $Eu != 0} {
      snack::sound sWork
      sWork read "$v(saveDir)/$fid.$v(ext)"
      sWork crop $S $E ;# 左右ブランク間のみを残す
      sWork write "$v(saveDir)/$fid.$v(ext)"

      ;# S値を補正
      if {$S != 0} {
        changeParam $paramU($r,1) 0 $r 1
      }

      ;# E値を補正
      if {$Eu != 0} {
        if {$v(setE) < 0} {
          set newE [cut3 [expr [sWork length -unit SECONDS] * -1000.0]]
          changeParam $paramU($r,5) $newE $r 5  ;# EをSからの時間で表す場合
        } else {
          changeParam $paramU($r,5) 0 $r 5      ;# Eを末尾からの時間で表す場合
        }
      }

      paramU2paramS $r
    }
  }
  ;# v(setS)を以前の値に戻す
  set v(setS) $setSbackup

  ;# 波形再読み込み、再描画
  set v(paramChanged) 1
  readWavFile
  Redraw all
}

#---------------------------------------------------
# 収録BGMの窓
#
proc bgmGuide {} {
  global v bgm t
  if [isExist .bgmg] return ;# 二重起動を防止
  toplevel .bgmg
  wm title .bgmg $t(bgmGuide,title)
  bind .bgmg <Escape> "destroy .bgmg"

  label .bgmg.lMode -text $t(bgmGuide,mode)
  radiobutton .bgmg.r1 -variable v(rec) -value 1 -command {bgm stop} \
    -text $t(bgmGuide,r1)
  radiobutton .bgmg.r2 -variable v(rec) -value 2 -command {bgm stop} \
    -text $t(bgmGuide,r2)
  radiobutton .bgmg.r3 -variable v(rec) -value 3 -command {bgm stop} \
    -text $t(bgmGuide,r3)
  radiobutton .bgmg.r4 -variable v(rec) -value 0 -command {bgm stop} \
    -text $t(bgmGuide,r4)

  label .bgmg.lWav -text $t(bgmGuide,bgm)
  frame .bgmg.fWav
  button .bgmg.fWav.b1 -textvar v(bgmFile) -relief solid -command {
    set fn [tk_getOpenFile -initialfile $v(bgmFile) \
            -title $t(bgmGuide,bTitle) -defaultextension "wav" \
            -filetypes { {{wav file} {.wav}} {{All Files} {*}} }]
    if {$fn != ""} {
      set v(bgmFile) $fn
      bgm stop
;#      set v(playMetroStatus) 0
    }
  }
  button .bgmg.fWav.b2 -image snackOpen -highlightthickness 0 -bg $v(bg) -command {
    set fn [tk_getOpenFile -initialfile $v(bgmFile) \
            -title $t(bgmGuide,bTitle) -defaultextension "wav" \
            -filetypes { {{wav file} {.wav}} {{All Files} {*}} }]
    if {$fn != ""} {
      set v(bgmFile) $fn
      bgm stop
;#      set v(playMetroStatus) 0
    }
  }
  button .bgmg.fWav.bp -text $t(bgmGuide,play) -bitmap snackPlay -command {
    testPlayBGM $v(bgmFile)
  }
  button .bgmg.fWav.bs -text $t(bgmGuide,stop) -bitmap snackStop -command {
    testStopBGM 
  }
  pack .bgmg.fWav.b1 -side left -fill x -expand 1
  pack .bgmg.fWav.b2 .bgmg.fWav.bp .bgmg.fWav.bs -side left

  frame .bgmg.fImg
  label .bgmg.fImg.l -text $t(bgmGuide,tplay)
  button .bgmg.fImg.bp -text $t(bgmGuide,play) -bitmap snackPlay -command {
    set ext [file extension $v(bgmFile)]
    testPlayBGM [file rootname $v(bgmFile)]-sample$ext
  }
  button .bgmg.fImg.bs -text $t(bgmGuide,stop) -bitmap snackStop -command {
    testStopBGM 
  }
  pack .bgmg.fImg.l .bgmg.fImg.bp .bgmg.fImg.bs -side left

  grid .bgmg.lMode   -row 0 -column 0 -sticky e
  grid .bgmg.r1      -row 0 -column 1 -sticky w
  grid .bgmg.r2      -row 1 -column 1 -sticky w
  grid .bgmg.r3      -row 2 -column 1 -sticky w
  grid .bgmg.r4      -row 3 -column 1 -sticky w

  grid .bgmg.lWav    -row 4 -column 0 -sticky e
  grid .bgmg.fWav    -row 4 -column 1 -sticky ewsn

  grid .bgmg.fImg    -row 5 -column 1 -sticky ewsn
}

#---------------------------------------------------
# 指定したファイルを読み込んで再生する
#
proc testPlayBGM {fname} {
  global bgm t
  if [snack::audio active] return
  if ![file exists $fname] {
    tk_messageBox -message "[eval format $t(testPlayBGM,errMsg)] (fname=$fname)" \
      -title $t(testPlayBGM,errTitle) -icon warning -parent .bgmg
    return
  }
  bgm read $fname
  bgm play
}

#---------------------------------------------------
# BGMを停止する
#
proc testStopBGM {} {
  global bgm t
  bgm stop
}

#---------------------------------------------------
# 先行発声チェック用の設定窓
#
proc uttTimingSettings {} {
  global v uttTiming uttTiming_bk t
  if [isExist .uts] return ;# 二重起動を防止
  toplevel .uts
  wm title .uts $t(uttTimingSettings,title)
  bind .uts <Escape> "destroy .uts"

  array set uttTiming_bk [array get uttTiming]     ;# パラメータバックアップ

  label .uts.lwav -text $t(uttTimingSettings,click)
  button .uts.bwav -textvar uttTiming(clickWav) -relief solid -command {
    set fn [tk_getOpenFile -initialfile $uttTiming(clickWav) \
            -title $t(uttTimingSettings,clickTitle) -defaultextension "wav" \
            -filetypes { {{wav file} {.wav}} {{All Files} {*}} }]
    if {$fn != ""} {
      set uttTiming(clickWav) $fn
    }
  }

  label .uts.l -text $t(uttTimingSettings,tempo)
  entry .uts.ebpm -textvar uttTiming(tempo) -validate key -validatecommand {
          if {![string is integer %P]} {return 0}
          if {%P <= 0} {return 0}
          set uttTiming(tempoMSec) [expr 60000.0 / double(%P)]
          set uttTiming(playMetroStatus) 0
          return 1
        }
  label .uts.lbpm -text $t(uttTimingSettings,bpm)
  label .uts.lbpmSec1 -textvar uttTiming(tempoMSec) -fg red
  label .uts.lbpmSec2 -text $t(uttTimingSettings,bpmUnit)

  label .uts.lpc -text $t(uttTimingSettings,clickNum)
  entry .uts.epc -textvar uttTiming(preCount) \
    -validate key -vcmd {string is int %P}
  label .uts.lpcunit -text $t(uttTimingSettings,clickUnit)

  label .uts.lmix -text $t(uttTimingSettings,mix)
  scale .uts.smix -variable uttTiming(mix) -orient horiz \
    -from 0 -to 1 -resolution .1 -showvalue 1

  frame  .uts.ctrl
  button .uts.ctrl.ok     -text $t(.confm.ok) -command {
    set ret [doUttTimingSettings]
    if {$ret == 0} {destroy .uts}
  }
  button .uts.ctrl.cancel -text $t(.confm.c) -command {
    array set uttTiming [array get uttTiming_bk]     ;# パラメータを以前の状態に戻す
    destroy .uts
  }
  button .uts.ctrl.apply -text $t(.confm.apply) -command {doUttTimingSettings}
  pack .uts.ctrl.ok .uts.ctrl.cancel .uts.ctrl.apply -side left

  grid .uts.lwav -row 0 -column 0 -sticky e
  grid .uts.bwav -row 0 -column 1 -columnspan 4 -sticky nesw

  grid .uts.l        -row 1 -column 0 -sticky e
  grid .uts.ebpm     -row 1 -column 1 -sticky nesw
  grid .uts.lbpm     -row 1 -column 2 -sticky w
  grid .uts.lbpmSec1 -row 1 -column 3 -sticky e
  grid .uts.lbpmSec2 -row 1 -column 4 -sticky w

  grid .uts.lpc      -row 2 -column 0 -sticky e
  grid .uts.epc      -row 2 -column 1 -sticky nesw
  grid .uts.lpcunit  -row 2 -column 2 -sticky w

  grid .uts.lmix     -row 3 -column 0 -sticky e
  grid .uts.smix     -row 3 -column 1 -sticky nesw

  grid .uts.ctrl     -row 4 -column 0 -columnspan 5 -sticky es
}

#---------------------------------------------------
# 先行発声チェック用の設定
#
proc doUttTimingSettings {} {
  global v uttTiming t

  if {$uttTiming(preCount) > 20} {
    tk_messageBox -title $t(.confm.errTitle) -icon warning \
      -message [eval format $t(doUttTimingSettings,errMsg)]
    return 1
  }
  if {$uttTiming(preCount) < 0} {
    tk_messageBox -title $t(.confm.errTitle) -icon warning \
      -message [eval format $t(doUttTimingSettings,errMsg2)]
    return 1
  }

  snack::sound oneClick
  oneClick read $uttTiming(clickWav)
  if {[oneClick cget -channels] != 1} {
    set oneClick [oneClick convert -channels Mono]
  }

  set beatLength [expr int($v(sampleRate) * 60.0 / $uttTiming(tempo))] ;# 単位=sample
  oneClick crop 0 [expr $beatLength -1]   ;# クリック音を一拍分の長さに切り落とす

  uttTiming(clickSnd) read $uttTiming(clickWav)
  if {[uttTiming(clickSnd) cget -channels] != 1} {
    set uttTiming(clickSnd) [uttTiming(clickSnd) convert -channels Mono]
  }
  uttTiming(clickSnd) crop 0 [expr $beatLength -1]   ;# クリック音を一拍分の長さに切り落とす
  for {set i 0} {$i < $uttTiming(preCount)} {incr i} {
    uttTiming(clickSnd) concatenate oneClick
  }
  return 0
}

#---------------------------------------------------
# 先行発声を試聴
#
proc playUttTiming {w} {
  global paramS paramU v uttTiming t

  ;# 試聴対象の行番号リストを作成
  set checkList {}
  foreach pos [$w curselection] {
    set r [lindex [split $pos ","] 0]
    if {[lindex $checkList end] != $r} {
      lappend checkList $r
    }
  }
  if {[llength $checkList] <= 0} {
    lappend checkList $v(listSeq)
  } elseif {[llength $checkList] > 20} {
    tk_messageBox -title $t(.confm,warnTitle) -icon warning \
      -message $t(playUttTiming,msg)

    set checkList [lrange $checkList 0 19]
  }

  set beatLength [expr int($v(sampleRate) * 60.0 / $uttTiming(tempo))] ;# 単位=sample
  snack::sound sWork2 -rate $v(sampleRate)
  sWork2 copy uttTiming(clickSnd)
  sWork2 flush
  foreach r $checkList {
    set fid $paramU($r,0)
    set S  0
    set E -1
    if {[uttTiming(clickSnd) length] <= 0} {
      set ret [doUttTimingSettings]
      if $ret return
    }
    if {[array get paramS "$r,S"] != ""} {
      set S [expr int($v(sampleRate) * $paramS($r,S))]
    }
    if {[array get paramS "$r,E"] != ""} {
      set E [expr int($v(sampleRate) * $paramS($r,E))]
    }
    if {[array get paramS "$r,P"] != ""} {
      set P [expr int($v(sampleRate) * $paramS($r,P))]
    } else {
      set P $S
    }

    set SandP [expr $P - $S]
    set mixStart [expr $uttTiming(preCount) * $beatLength - $SandP + [sWork2 length]]
    ;# もしS〜Pの長さが一拍より長い場合は一拍分だけを切り出すよう補正する
    if {$SandP > $beatLength} {
      set mixStart [expr ($uttTiming(preCount) - 1) * $beatLength + [sWork2 length]]
      set S [expr int($P - $beatLength)]
    } elseif {$SandP > [expr $beatLength / 2]} {
      ;# もしS〜Pの長さが半拍より長い場合は半拍分だけを切り出すよう補正する
      set mixStart [expr ($uttTiming(preCount) - 1) * $beatLength + $beatLength/2 + [sWork2 length]]
      set S [expr int($P - $beatLength)]
    } 

    snack::sound sWork
    sWork read "$v(saveDir)/$fid.$v(ext)"
    if {[sWork cget -channels] != 1} {
      set sWork [sWork convert -channels Mono]
    }
    sWork crop $S $E ;# 左右ブランク間のみを残す

    sWork2 concatenate uttTiming(clickSnd)
    sWork2 mix sWork -start $mixStart -mixscaling $uttTiming(mix) -prescaling [expr 1.0 - $uttTiming(mix)]
  }

  sWork2 play
  ;# sWork2 write $v(saveDir)/a.$v(ext)
}


#---------------------------------------------------
# メトロノームの窓
#
proc tempoGuide {} {
  global v metro t
  if [isExist .tg] return ;# 二重起動を防止
  toplevel .tg
  wm title .tg $t(tempoGuide,title)
  bind .tg <Escape> "destroy .tg"

  bind .tg <KeyPress-m>     toggleMetroPlay
  bind .tg <KeyPress-M>     toggleMetroPlay

  label .tg.lwav -text $t(tempoGuide,click)
  button .tg.bwav -textvar v(clickWav) -relief solid -command {
    set fn [tk_getOpenFile -initialfile $v(clickWav) \
            -title $t(tempoGuide,clickTitle) -defaultextension "wav" \
            -filetypes { {{wav file} {.wav}} {{All Files} {*}} }]
    if {$fn != ""} {
      set v(clickWav) $fn
      metro stop
      set v(playMetroStatus) 0
    }
  }

  label .tg.l -text $t(tempoGuide,tempo)
  entry .tg.ebpm -textvar v(tempo) -validate key -validatecommand {
          if {![string is integer %P]} {return 0}
          if {%P <= 0} {return 0}
          set v(tempoMSec) [expr 60000.0 / double(%P)]
          metro stop
          set v(playMetroStatus) 0
          return 1
        }
  label .tg.lbpm -text $t(tempoGuide,bpm)
  label .tg.lbpmSec1 -textvar v(tempoMSec) -fg red
  label .tg.lbpmSec2 -text $t(tempoGuide,bpmUnit)
  label .tg.mes -text $t(tempoGuide,comment)

  grid .tg.lwav -row 0 -column 0 -sticky e
  grid .tg.bwav -row 0 -column 1 -columnspan 4 -sticky nesw

  grid .tg.l        -row 1 -column 0 -sticky e
  grid .tg.ebpm     -row 1 -column 1 -sticky nesw
  grid .tg.lbpm     -row 1 -column 2 -sticky w
  grid .tg.lbpmSec1 -row 1 -column 3 -sticky e
  grid .tg.lbpmSec2 -row 1 -column 4 -sticky w

  grid .tg.mes -row 2 -columnspan 5
}

#---------------------------------------------------
# 周波数を指定してsin波を再生する
#
proc pitchGuide {} {
  global v f0 t
  if [isExist .pg] return ;# 二重起動を防止
  toplevel .pg
  wm title .pg $t(pitchGuide,title)
  bind .pg <Escape> "destroy .pg"

  bind .pg <KeyPress-Up>    {changeTone 1}
  bind .pg <KeyPress-8>     {changeTone 1}
  bind .pg <KeyPress-Down>  {changeTone -1}
  bind .pg <KeyPress-2>     {changeTone -1}
  bind .pg <KeyPress-Left>  {changeTone -12}
  bind .pg <KeyPress-4>     {changeTone -12}
  bind .pg <KeyPress-Right> {changeTone 12}
  bind .pg <KeyPress-6>     {changeTone 12}
  bind .pg <KeyPress-o>     toggleOnsaPlay
  bind .pg <KeyPress-O>     toggleOnsaPlay

  packToneList .pg.tl $t(pitchGuide,sel) guideTone guideOctave guideFreqTmp 10 guideVol

  pack [frame .pg.vl] -fill x
  label .pg.vl.l -text $t(pitchGuide,vol)
  scale .pg.vl.s -from 0 -to 32768 -show no -var f0(guideVol) -orient horiz
  pack .pg.vl.l -side left -anchor nw
  pack .pg.vl.s -side left -anchor nw -fill x -expand 1

  ;# 各音名に対応する周波数を自動計算して表示する(非常に汚いやり方)
  ;# 音名orオクターブに変化があれば周波数計算を行う
  ;# 周波数をf0(guideFreq)でなくf0(guideFreqTmp)に入れるのは、
  ;#「OK」or「適用」ボタンを押すまで値変更を反映させないため。
  set f0(guideFreqTmp) [tone2freq "$f0(guideTone)$f0(guideOctave)"]
  trace variable f0 w calcGuideFreq
  proc calcGuideFreq {var elm mode} {
    global f0 t
    switch $elm {
      "guideTone" -
      "guideOctave" {
        set f0(guideFreqTmp) [tone2freq "$f0(guideTone)$f0(guideOctave)"]
      }
    }
  }
  bind .pg <Destroy> { trace vdelete f0 w calcGuideFreq }
}

#---------------------------------------------------
# 指定した周波数[Hz]のsin波を再生する
#
proc playSin {freq vol length} {
  global v onsa t
  if [snack::audio active] return
  if $::debug {puts $freq}
  if {$freq > 10 && $vol > 0} { 
#    set f [snack::filter generator $freq $vol 0.0 sine $v(sampleRate)]
    set g  [snack::filter generator $freq $vol 0.01 triangle $length]
    set f1 [snack::filter formant 500 50]
    set f2 [snack::filter formant 1500 75]
    set f3 [snack::filter formant 2500 100]
    set f4 [snack::filter formant 3500 150]
    set f  [snack::filter compose $g $f1 $f2 $f3 $f4]
    onsa play -filter $f -command "$f destroy" 
  }
}

#---------------------------------------------------
# 指定した番号の音の収録に移動
#
proc jumpRec {index} {
  global v rec t paramU

  set v(msg) "$v(listSeq) / $paramU(size_1)"  ;# 現在何個目のデータを処理しているかを表示
  if {$v(recSeq) == $index} {
    Redraw param    ;# 連続音の場合wavは変わらないがパラメータは変わるため
    return
  }
  set v(recSeq) $index
  set v(recLab) [lindex $v(recList) $v(recSeq)] 
  set v(recStatus) 0
  readWavFile
  Redraw all
}

#---------------------------------------------------
# 自動収録した連続発声からoto.iniを生成
#
proc genParam {} {
  global v genWindow genParam t

  if [isExist $genWindow] return ;# 二重起動を防止
  toplevel $genWindow
  wm title $genWindow $t(genParam,title)
  bind $genWindow <Escape> "destroy $genWindow"

  set r 0

  # 初期設定
  set f($r) [labelframe $genWindow.f($r) -relief groove -padx 5 -pady 5]

  label $f($r).lB  -text $t(genParam,tempo)
  entry $f($r).eB  -textvar genParam(bpm) -wi 10
  label $f($r).lBU -text $t(genParam,bpm)

  label $f($r).lS  -text $t(genParam,S)
  entry $f($r).eS  -textvar genParam(S) -wi 10
  label $f($r).lSU -text $t(genParam,unit)
  tk_optionMenu $f($r).mSU genParam(SU) msec $t(genParam,haku)

  grid  $f($r).lB  -row 0 -column 0 -sticky nse
  grid  $f($r).eB  -row 0 -column 1 -sticky nse
  grid  $f($r).lBU -row 0 -column 2 -sticky nsw -columnspan 2
  grid  $f($r).lS  -row 1 -column 0 -sticky nse
  grid  $f($r).eS  -row 1 -column 1 -sticky nse
  grid  $f($r).lSU -row 1 -column 2 -sticky nsw
  grid  $f($r).mSU -row 1 -column 3 -sticky nsw
  incr r

  # ボタン
  set f($r) [frame $genWindow.f($r) -padx 5 -pady 0]
  label  $f($r).arrow1 -text $t(genParam,darrow)
  button $f($r).bInit  -text $t(genParam,bInit)  -command initGenParam
  label  $f($r).arrow2 -text $t(genParam,darrow)

  grid  $f($r).arrow1 -row 0 -column 0 -sticky nsew
  grid  $f($r).bInit  -row 1 -column 0 -sticky nsew
  grid  $f($r).arrow2 -row 2 -column 0 -sticky nsew
  incr r

  # msec単位での各設定
  set f($r) [labelframe $genWindow.f($r) -relief groove -padx 5 -pady 5]
  label $f($r).lO  -text $t(genParam,O)
  entry $f($r).eO  -textvar genParam(O) -wi 10
  label $f($r).lOU -text $t(genParam,msec)

  label $f($r).lP  -text $t(genParam,P)
  entry $f($r).eP  -textvar genParam(P) -wi 10
  label $f($r).lPU -text $t(genParam,msec)

  label $f($r).lC  -text $t(genParam,C)
  entry $f($r).eC  -textvar genParam(C) -wi 10
  label $f($r).lCU -text $t(genParam,msec)

  label $f($r).lE  -text $t(genParam,E)
  entry $f($r).eE  -textvar genParam(E) -wi 10
  label $f($r).lEU -text $t(genParam,msec)

  grid  $f($r).lO    -row 0 -column 0 -sticky nse
  grid  $f($r).eO    -row 0 -column 1 -sticky nse
  grid  $f($r).lOU   -row 0 -column 2 -sticky nse
  grid  $f($r).lP    -row 1 -column 0 -sticky nse
  grid  $f($r).eP    -row 1 -column 1 -sticky nse
  grid  $f($r).lPU   -row 1 -column 2 -sticky nse
  grid  $f($r).lC    -row 2 -column 0 -sticky nse
  grid  $f($r).eC    -row 2 -column 1 -sticky nse
  grid  $f($r).lCU   -row 2 -column 2 -sticky nse
  grid  $f($r).lE    -row 3 -column 0 -sticky nse
  grid  $f($r).eE    -row 3 -column 1 -sticky nse
  grid  $f($r).lEU   -row 3 -column 2 -sticky nse
  incr r

  # パワーに基づく先行発声位置の自動推定用の設定窓
  set f($r) [labelframe $genWindow.f($r) -relief groove -padx 5 -pady 5]
  checkbutton $f($r).cb -variable genParam(autoAdjustRen) -text $t(genParam,autoAdjustRen)
  label $f($r).lv  -text $t(genParam,vLow)
  entry $f($r).ev  -textvar genParam(vLow) -wi 10
  label $f($r).lvu -text $t(genParam,db)
  label $f($r).ls  -text $t(genParam,sRange)
  entry $f($r).es  -textvar genParam(sRange) -wi 10
  label $f($r).lsu -text $t(genParam,msec)
  label $f($r).lb -text $t(genParam,f0pow)

  grid  $f($r).cb  -row 0 -column 0 -sticky nsw -columnspan 3
  grid  $f($r).lv  -row 1 -column 0 -sticky nse
  grid  $f($r).ev  -row 1 -column 1 -sticky nse
  grid  $f($r).lvu -row 1 -column 2 -sticky nsw
  grid  $f($r).ls  -row 2 -column 0 -sticky nse
  grid  $f($r).es  -row 2 -column 1 -sticky nse
  grid  $f($r).lsu -row 2 -column 2 -sticky nsw
  grid  $f($r).lb  -row 3 -column 0 -sticky nsw -columnspan 3
  incr r

  # MFCCに基づく先行発声位置の自動推定の設定窓
  set f($r) [labelframe $genWindow.f($r) -relief groove -padx 5 -pady 5]
  checkbutton $f($r).cb -variable genParam(autoAdjustRen2) -text $t(genParam,autoAdjustRen2)
  label $f($r).lv  -text $t(genParam,autoAdjustRen2Opt)
  entry $f($r).ev  -textvar genParam(autoAdjustRen2Opt) -wi 35
  label $f($r).lm  -text $t(genParam,autoAdjustRen2Pattern)
  entry $f($r).em  -textvar genParam(autoAdjustRen2Pattern) -wi 35

  grid  $f($r).cb  -row 0 -column 0 -sticky nsw -columnspan 3
  grid  $f($r).lv  -row 1 -column 0 -sticky nse
  grid  $f($r).ev  -row 1 -column 1 -sticky nsew -columnspan 2
  grid  $f($r).lm  -row 2 -column 0 -sticky nse
  grid  $f($r).em  -row 2 -column 1 -sticky nsew -columnspan 2
  incr r

  # エイリアス重複に関する設定欄
  set f($r) [labelframe $genWindow.f($r) -relief groove -padx 5 -pady 5]

  set genParam(useAliasMax) 0  ;# 0=重複をそのままにする,1=通し番号を付ける
  set genParam(aliasMax) 0     ;# 重複番号の最大値
  label $f($r).lt -text $t(genParam,aliasMax)
  radiobutton $f($r).rm1 -variable genParam(useAliasMax) -value  0 -text $t(genParam,aliasMaxNo)
  radiobutton $f($r).rm2 -variable genParam(useAliasMax) -value  1 -text $t(genParam,aliasMaxYes)
  label $f($r).ll   -text $t(genParam,aliasMaxNum)
  entry $f($r).e    -textvar genParam(aliasMax) -wi 10

  grid $f($r).lt    -row 0 -column 0 -sticky nsw
  grid $f($r).rm1   -row 1 -column 0 -sticky nsw
  grid $f($r).rm2   -row 1 -column 1 -sticky nsw
  grid $f($r).ll    -row 2 -column 0 -sticky nse
  grid $f($r).e     -row 2 -column 1 -sticky nse
  incr r

  # 実行ボタン
  set f($r) [frame $genWindow.f($r) -padx 5 -pady 0]

  label  $f($r).arrow2 -text $t(genParam,darrow)
  button $f($r).bs -text $t(genParam,do) -command {
    if {$v(appname) == "OREMO"} {
      doGenParamForOREMO
      saveParamFile
    } else {
      doGenParam
    }
    destroy $genWindow
  }
  button $f($r).bc -text $t(.confm.c) -command {destroy $genWindow}

  grid  $f($r).arrow2 -row 0 -column 0 -columnspan 2 -sticky nsew
  grid  $f($r).bs     -row 1 -column 0 -sticky nsew
  grid  $f($r).bc     -row 1 -column 1 -sticky nsew
  incr r

  for {set i 0} {$i < $r} {incr i} {
    pack $f($i) -anchor nw -padx 2 -pady 2 -expand 1 -fill x
  }

  raise $genWindow
  focus $genWindow
}

#---------------------------------------------------
# BPM、冒頭Sからパラメータの初期値を求める
#
proc initGenParam {} {
  global genParam t

  set mspb [expr 60000.0 / $genParam(bpm)]  ;# 1拍の長さ[msec]を求める

  set genParam(O) [cut3 [expr $mspb / 6.0]]
  set genParam(P) [cut3 [expr $mspb / 2.0]]
  set genParam(C) [cut3 [expr $mspb * 3.0 / 4.0]]
  set genParam(E) [cut3 [expr - ($mspb + $genParam(O)) ]]
  set genParam(sRange) $genParam(P)
  ;#set genParam(E) [cut3 [expr - $mspb ]]
}

#---------------------------------------------------
# 連続発声の先頭モーラの先行発声位置を推定し、
# 先行発声がその場所になるための補正量(sec)を返す
# Porg, rangeの単位：sec。Porg-range〜Porg+rangeの範囲を探索する
#
proc autoAdjustRen {fid Porg range} {
  global v t f0 power genParam

  snack::sound sWork
  if {[file exists "$v(saveDir)/$fid.$v(ext)"]} {
    sWork read "$v(saveDir)/$fid.$v(ext)"
  }

  if {$Porg > $range} {
    set Lsec [expr $Porg - $range]
  } else {
    set Lsec 0
  }

  ;# 有声開始位置を求める
  set seriestmp {}
  set start [expr int($Lsec * $v(sampleRate))]
  set end   [expr int(($Porg + $range) * $v(sampleRate))]
  if {[catch {set seriestmp [sWork pitch -method $f0(method) \
    -framelength $f0(frameLength) -windowlength $f0(windowLength) \
    -maxpitch $f0(max) -minpitch $f0(min) \
    -start $start -end $end \
    ] } ret]} {
    if {$ret != ""} {
      puts "error: $ret"
    }
    set seriestmp {}
  }
  set f0old 1
  for {set i 0} {$i < [llength $seriestmp]} {incr i} {
    set f0now [lindex [split [lindex $seriestmp $i] " "] 0]
    if {$f0old <= 0 && $f0now > 0} break  ;# 直前が無声、当該が有声ならbreak
    set f0old $f0now
  }
  if {$i < [llength $seriestmp]} {
    ;# 有声開始点であればそこを先行発声候補とし、更に次のパワーに基づく
    ;# 探索開始点にする
    set Pnew [expr $i * $f0(frameLength) + $Lsec]
    set Lsec $Pnew
    set start [expr int($Lsec * $v(sampleRate))]   ;# end はF0と同じ
  } else {
    set Pnew $Porg
  }

  ;# 固定範囲〜右ブランク間の平均パワーavePを求める。ただし値が30個以上なら
  ;# 30個で平均を求める
  set Nmax 30
  set N 0
  set aveP 0
  set avePstart [expr int(($Pnew + ($genParam(C) - $genParam(P)) / 1000.0) * $v(sampleRate))]
  set avePend   [expr int(($Pnew + (abs($genParam(E)) - $genParam(P)) / 1000.0) * $v(sampleRate))]
  set pw [sWork power -framelength $power(frameLength) \
    -windowtype $power(window) -preemphasisfactor $power(preemphasis) \
    -windowlength [expr int($power(windowLength) * $v(sampleRate))] \
    -start $start -end $end]
  for {set i 0} {$i < [llength $pw] && $i < $Nmax} {incr i} {
    set aveP [expr $aveP + [lindex $pw $i]]
    incr N
  }
  if {$N > 0} {
    set aveP [expr $aveP / $N]
  }

  ;# 探索区間のパワーを求める
  set pw [sWork power -framelength $power(frameLength) \
    -windowtype $power(window) -preemphasisfactor $power(preemphasis) \
    -windowlength [expr int($power(windowLength) * $v(sampleRate))] \
    -start $start -end $end]

  ;# 閾値に対してパワー曲線が右上がりにクロスする点を探す
  ;# votから右に行き、パワーが右上がりに閾値を超える所まで移動する
  ;# そのような場所が複数ある場合は凹みがより深いものにする
  set vLow [expr $aveP - $genParam(vLow)]
  set powOld $vLow
  set pn -1                         ;# 先行発声位置を入れる変数
  set pnMin     10001               ;# 先行発声前の凹みの深さ
  set powNowMin 10000
  for {set i 0} {$i < [llength $pw]} {incr i} {
    set powNow [lindex $pw $i]
    if {$powNow < $powNowMin} {
      set powNowMin $powNow         ;# 凹みの値を求めていく
    }
    if {$powOld < $vLow && $powNow >= $vLow && $powNowMin < $pnMin} {
      set pn $i
      set pnMin $powNowMin       ;# 凹みの値を保存
      set powNowMin 10000        ;# 再初期化
    }
    set powOld $powNow
  }

  #if {$pn < 0 && [expr $genParam(avePPrev) - $genParam(vLow) - $aveP] > 0} {
  #  ;# パワー凹みを見つけられず、かつ先行モーラより当該モーラの平均パワーがある程度小さい場合、
  #  ;# votから右に行き、パワー曲線が当該モーラの平均パワーにクロスする点を探す
  #  for {set i 0} {$i < [llength $pw]} {incr i} {
  #    set powNow [lindex $pw $i]
  #    if {$powNow <= $aveP} {
  #      set pn $i
  #      break
  #    }
  #  }
  #}

  if {$pn >= 0} {
    set Pnew [expr $pn * $power(frameLength) + $Lsec]
  }

#koko,もう一つ規則を作るなら、上記パワーの凹みが検出できない場合に
#現在のPnewの直近の凹み(x(i-1) >= x(i) < x(i+1) なi)を探すとか。

#koko,平均パワーが先行モーラより大きい場合の規則。

  set genParam(avePPrev) $aveP    ;# 次回の推定のために平均パワーを保存
 
  ;# 現在位置が先行発声になるための補正量(sec)を返す
  return [expr $Pnew - $Porg]
}

#---------------------------------------------------
# もしSが負なら他のパラメータ位置が変わらないようにS=0にする
# ※本ルーチンは連続音用(genParam用)
# ※本ルーチンではparamUのみ訂正し、paramSには反映させないことに注意
#
proc setSto0 {r} {
  global paramU

  if {$paramU($r,1) < 0} {     ;# S < 0 なら
    ;# E(負) の値を修正
    set paramU($r,5) [cut3 [expr $paramU($r,5) - $paramU($r,1)]]
    ;# C の値を修正
    set paramU($r,4) [cut3 [expr $paramU($r,4) + $paramU($r,1)]]
    ;# P の値を修正
    set paramU($r,3) [cut3 [expr $paramU($r,3) + $paramU($r,1)]]
    ;# O の値を修正
    set paramU($r,2) [cut3 [expr $paramU($r,2) + $paramU($r,1)]]
    if {$paramU($r,2) < 0} {
      set paramU($r,2) 0
    }
    ;# S を0にする
    set paramU($r,1) 0
  }
}

#---------------------------------------------------
# 連続発声のパラメータを自動生成する
#
proc doGenParam {} {
  global genParam v snd paramS paramU paramUsize t

  if $v(paramChanged) {
    set act [tk_dialog .confm $t(.confm) $t(.confm.delParam) \
      question 1 $t(.confm.yes) $t(.confm.no)]
    if {$act == 1} return
  }
  initParamS
  initParamU 1
  snack::sound sWork

  set mspb [expr 60000.0 / $genParam(bpm)]  ;# 1拍の長さ[msec]を求める

  # 1モーラ目の開始位置[ms]を求める
  if {$genParam(SU) == "msec"} {
    set Sstart $genParam(S)
  } else {
    set mspb [expr 60000.0 / $genParam(bpm)]  ;# 1拍の長さ[msec]を求める
    set Sstart [expr $genParam(S) * $mspb]
  }

  initProgressWindow
  set procStart [clock seconds]

  array unset aliasChoufuku   ;# エイリアス重複数を入れる
  set recListMax [llength $v(recList)]
  for {set recListSeq 0} {$recListSeq < $recListMax} {incr recListSeq} {
    set fid [lindex $v(recList) $recListSeq]
#koko,部分推定可能にすべき。
    set S $Sstart
    set morae [getMorae [string trimleft $fid "_"]]
    set genParam(avePPrev) 0    ;# 平均パワーを初期化
    set fname ""
    set fnameOld ""
    for {set i 0} {$i < [llength $morae]} {incr i} {
      ;# 当該モーラが休符(_)であればSを次の位置に移動して次のモーラへ
      set mora [lindex $morae $i]
      if {$mora == "_"} {
        set S [expr $S + $mspb]   ;# Sを次の位置に移動
        continue                  ;# 登録せず次のモーラへ
      }
      ;# エイリアスの決定
      set alias [getRenAlias $morae $i]
      ;# エイリアス重複数をチェック
      if {$genParam(useAliasMax) && [array names aliasChoufuku $alias] != ""} {
        incr aliasChoufuku($alias)
        if {$genParam(aliasMax) <= 0 || $aliasChoufuku($alias) <= $genParam(aliasMax)} {
          set alias "$alias$aliasChoufuku($alias)"
        } else {
          set S [expr $S + $mspb]   ;# Sを次の位置に移動
          continue                  ;# 重複が上限を超えたので登録せず次へ。
        }
      } else {
        set aliasChoufuku($alias) 1
      }
      # Sの位置補正
      if $genParam(autoAdjustRen) {
        ;# 先行発声位置を自動推定し、その差分をSに加える
        set Psec [expr ($S + $genParam(P)) / 1000.0]
        set range [expr $genParam(sRange) / 1000.0]
        set S [cut3 [expr $S + 1000.0 * [autoAdjustRen $fid $Psec $range]]]
      }
      set paramU($paramUsize,0) $fid           ;# fid
      set paramU($paramUsize,6) $alias         ;# A
      set paramU($paramUsize,1) $S             ;# S
      set paramU($paramUsize,4) $genParam(C)   ;# C
      set paramU($paramUsize,5) $genParam(E)   ;# E
      set paramU($paramUsize,3) $genParam(P)   ;# P
      set paramU($paramUsize,2) $genParam(O)   ;# O
      set paramU($paramUsize,R) $recListSeq    ;# recListの配列番号

      setSto0 $paramUsize    ;# Sが負の場合は他のパラメータ位置を動かさず0に修正

      set S [expr $S + $mspb]   ;# Sを次の位置に移動

      ;# paramSを求める
      set fname "$v(saveDir)/$fid.$v(ext)"
      if [file exists "$fname"] {
        if {$fname != $fnameOld} {
          sWork read "$fname"
          set v(sndLength) [sWork length -unit SECONDS]
          set fname $fnameOld
        }
        paramU2paramS $paramUsize
      }

      incr paramUsize
    }
    # もしwavファイル名が「息.wav」など平仮名の一切ない名前だった場合、上のfor文が実行されず
    # oto.iniにwavのエントリ自体が無くなってしまう。そこで以下でエントリを一つ追加する。
    if {[llength $morae] <= 0} {
      set paramU($paramUsize,0) $fid           ;# fid
      set paramU($paramUsize,6) ""             ;# A
      set paramU($paramUsize,1) 0              ;# S
      set paramU($paramUsize,4) 0              ;# C
      set paramU($paramUsize,5) 0              ;# E
      set paramU($paramUsize,3) 0              ;# P
      set paramU($paramUsize,2) 0              ;# O
      set paramU($paramUsize,R) $recListSeq    ;# recListの配列番号
      incr paramUsize
    }

    # MFCCに基づくSの位置補正
    if {$genParam(autoAdjustRen2) && [llength $morae] > 0} {
      autoAdjustRen2 [expr $paramUsize - [llength $morae]]  [expr $paramUsize - 1]
    }

    set remain [expr ([clock seconds] - $procStart) * ($recListMax - $recListSeq) / ($recListSeq + 1)]
    updateProgressWindow [expr 100 * $recListSeq / [llength $v(recList)]] "($recListSeq / $recListMax) (remain: $remain sec)"
  }

  deleteProgressWindow
  resetDisplay
  ;# 一覧表のサイズを更新する
  if [winfo exists .entpwindow] {
    .entpwindow.t configure -rows $paramUsize
    setCellSelection
  }
  set v(paramChanged) 1
  setEPWTitle
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存
  set v(msg) [eval format $t(doGenParam,doneMsg)]
}

#---------------------------------------------------
# 指定範囲内のパラメータをMFCCで補正する
#
proc autoAdjustRen2sel {} {
  global paramU v

  set targetList {}
  foreach pos [.entpwindow.t curselection] {
    set r [lindex [split $pos ","] 0]
    if {[lindex $targetList end] != $r} {
      lappend targetList $r
    }
  }
  if {[llength $targetList] <= 0} return

  set start [lindex $targetList 0]
  set r $start
  set i 0
  while {$i < [llength $targetList]} {
    set r [lindex $targetList $i]
    if {$paramU($start,0) != $paramU($r,0)} {
      autoAdjustRen2 $start [expr $r - 1]
      set start $r
    }
    incr i
  }
  autoAdjustRen2 $start $r

  Redraw param
  set v(paramChanged) 1
}

#---------------------------------------------------
# MFCCに基づくSの位置補正
#
proc autoAdjustRen2 {start end} {   ;# start番目からend番目(end-1までではない)のデータを処理する
  global genParam v paramS paramU paramUsize t topdir

  set pattern [split $genParam(autoAdjustRen2Pattern) " "]

  ;# 適用すべきモーラが一つもなければreturn
  if {$genParam(autoAdjustRen2Pattern) != ""} {
    for {set f $start} {$f <= $end} {incr f} {
      set mora [lindex [getMorae [lindex [split $paramU($f,6) " "] 1]] 0]
      if {[lsearch $pattern $mora] >= 0} break
    }
    if {$f > $end} return
  }

  set f $start
  while {$f <= $end} {
    set fname "$v(saveDir)/$paramU($f,0).$v(ext)"
    set opt ""

    set d 0
    for {set i $f} {$i <= $end && $i < $paramUsize && $paramU($f,0) == $paramU($i,0)} {incr i} {
      set tmp [expr $paramS($i,E) * 0.25 + $paramS($i,P) * 0.75]
      set opt "$opt $tmp"
      incr d
    }
    set ret [eval exec "./tools/modifyPre.exe" $genParam(autoAdjustRen2Opt) "$fname" $opt]
    if {$ret != -1} {
      set newP [split $ret "\n"]
      set r $f
      for {set i 0} {$i < [llength $newP]} {incr i} {
        ;# 適用すべきモーラでなければskip
        set ftmp [expr $f + $i]
        set pre  [lindex [split $paramU($ftmp,6) " "] 0]
        set mora [lindex [getMorae [lindex [split $paramU($ftmp,6) " "] 1]] 0]
        if {$genParam(autoAdjustRen2Pattern) != ""} {
          if {$pre == "-" || [lsearch $pattern $mora] < 0 || $pre == [getVowel $mora]} {
            incr r
            continue
          }
        }

        set S [cut3 [expr ($paramS($r,S) + [lindex $newP $i] - $paramS($r,P)) * 1000.0]]
        if {$S >= 0} {
          set paramU($r,1) $S             ;# S
          paramU2paramS $r
        } else {
          set paramU($r,1) 0             ;# S
          set paramU($r,3) [cut3 [expr $paramU($r,3) + $S]] ;# P
          paramU2paramS $r
        }
        incr r
      }
    }
    set f [expr $f + $d]
  }
}

#---------------------------------------------------
# モーラ列moraeの指定した位置iから連続音用のエイリアス(「a い」など)を求めて返す
#
proc getRenAlias {morae i} {
  global genParam v paramS paramU paramUsize t

  set mora [lindex $morae $i]
  if {$i == 0} {
    set prev "-"
  } else {
    set prev [getVowel [lindex $morae [expr $i - 1]]]
  }
  return "$prev $mora"
}

#---------------------------------------------------
# 文字列を1モーラに分解して返す
#
proc getMorae {inMorae} {
  set morae {}
  for {set i 0} {$i < [string length $inMorae]} {incr i} {
    set char [string range $inMorae $i $i]
    if [isKana $char] {
      if [isMora $char] {
        ;# 現在の$charは一モーラなのでリストに追加
        lappend morae $char
      } else {
        set last [expr [llength $morae] -1]
        set mora "[lindex $morae $last]$char"
        set morae [lreplace $morae $last $last $mora]
      }
    }
  }
  return $morae
}

#---------------------------------------------------
# 一モーラの母音部の音素を返す
#
proc getVowel {mora} {
  set last [expr [string length $mora] -1]
  set char [string range $mora $last $last]

  set vA {あ か さ た な は ま や ら わ が ざ だ ば ぱ ゃ ぁ ゎ \
          ア カ サ タ ナ ハ マ ヤ ラ ワ ガ ザ ダ バ パ ャ ァ ヮ }
  set vI {い き し ち に ひ み    り    ぎ じ ぢ び ぴ    ぃ ゐ \
          イ キ シ チ ニ ヒ ミ    リ    ギ ジ ヂ ビ ピ    ィ ヰ }
  set vU {う く す つ ぬ ふ む ゆ る    ヴ ぐ ず づ ぶ ぷ ぅ ゅ っ \
          ウ ク ス ツ ヌ フ ム ユ ル       グ ズ ヅ ブ プ ゥ ュ ッ }
  set vE {え け せ て ね へ め    れ    げ ぜ で べ ぺ    ぇ ゑ \
          エ ケ セ テ ネ ヘ メ    レ    ゲ ゼ デ ベ ペ    ェ ヱ }
  set vO {お こ そ と の ほ も よ ろ を ご ぞ ど ぼ ぽ ょ ぉ    \
          オ コ ソ ト ノ ホ モ ヨ ロ ヲ ゴ ゾ ド ボ ポ ョ ォ    }
  set vN {ん ン}
  set vR {_}

  if {[lsearch $vA $char] >= 0} { return "a" }
  if {[lsearch $vI $char] >= 0} { return "i" }
  if {[lsearch $vU $char] >= 0} { return "u" }
  if {[lsearch $vE $char] >= 0} { return "e" }
  if {[lsearch $vO $char] >= 0} { return "o" }
  if {[lsearch $vN $char] >= 0} { return "n" }
  if {[lsearch $vR $char] >= 0} { return "-" }   ;# 休符だった場合
}

#---------------------------------------------------
# charが平仮名または片仮名なら1を、それ以外なら0を返す
#
proc isKana {char} {
  set kanaList {あ か さ た な は ま や ら わ    が ざ だ ば ぱ ゃ ぁ ゎ \
                ア カ サ タ ナ ハ マ ヤ ラ ワ    ガ ザ ダ バ パ ャ ァ ヮ \
                い き し ち に ひ み    り       ぎ じ ぢ び ぴ    ぃ ゐ \
                イ キ シ チ ニ ヒ ミ    リ       ギ ジ ヂ ビ ピ    ィ ヰ \
                う く す つ ぬ ふ む ゆ る       ぐ ず づ ぶ ぷ ゅ ぅ っ \
                ウ ク ス ツ ヌ フ ム ユ ル    ヴ グ ズ ヅ ブ プ ュ ゥ ッ \
                え け せ て ね へ め    れ       げ ぜ で べ ぺ    ぇ ゑ \
                エ ケ セ テ ネ ヘ メ    レ       ゲ ゼ デ ベ ペ    ェ ヱ \
                お こ そ と の ほ も よ ろ を    ご ぞ ど ぼ ぽ ょ ぉ    \
                オ コ ソ ト ノ ホ モ ヨ ロ ヲ    ゴ ゾ ド ボ ポ ョ ォ    \
                ん ン ゛ ゜ °\
                _ }
  if {[lsearch $kanaList $char] >= 0} {
    return 1
  } else {
    return 0
  }
}

#---------------------------------------------------
# charが一モーラなら1を、拗音などなら0を返す
#
proc isMora {char} {
  set notMora {ぁ ぃ ぅ ぇ ぉ ゃ ゅ ょ ゎ っ \
               ァ ィ ゥ ェ ォ ャ ュ ョ ヮ ッ ゛ ゜ °}
  if {[lsearch $notMora $char] >= 0} {
    return 0
  } else {
    return 1
  }
}

#---------------------------------------------------
# 現在のパラメータ配列を登録する
#
proc setUndoPoint {} {
  global v paramU paramS paramU_bk paramS_bk paramUsize

  ;# アンドゥポイントの内容と同じかどうかチェック
  set diff 0
  for {set r 1} {$r < $paramUsize} {incr r} {
    for {set c 0} {$c < 7} {incr c} {
      if {[array names paramU    $r,$c] == "" && \
          [array names paramU_bk $paramU_bk(i),$r,$c] == ""} continue

      if {[array names paramU    $r,$c] != "" && \
          [array names paramU_bk $paramU_bk(i),$r,$c] != ""} {
        if {$paramU($r,$c) != $paramU_bk($paramU_bk(i),$r,$c)} {
          set diff 1
          break
        }
      } else {
        set diff 1
        break
      }
    }
  }
  if {$diff == 0} {return 0}   ;# 内容が同じならここで終了

  incr paramS_bk(i)
  incr paramU_bk(i)
  foreach k [array names paramS_bk $paramS_bk(i),*] {
    unset paramS_bk($k)
  }
  foreach k [array names paramU_bk $paramU_bk(i),*] {
    unset paramU_bk($k)
  }
  foreach k [array names paramS] {
    set paramS_bk($paramS_bk(i),$k) $paramS($k)
  }
  foreach k [array names paramU] {
    set paramU_bk($paramU_bk(i),$k) $paramU($k)
  }
  set paramS_bk(N) $paramS_bk(i)
  set paramU_bk(N) $paramU_bk(i)
  return 1
}

#---------------------------------------------------
# パラメータ配列のアンドゥ
#
proc undoParam {} {
  global v paramU paramS paramU_bk paramS_bk

  if {$paramS_bk(i) < 0} return
  setUndoPoint
  incr paramS_bk(i) -1
  incr paramU_bk(i) -1
  if {$paramS_bk(i) < 0} {
    set paramS_bk(i) 0
    set paramU_bk(i) 0
  }
 
  array unset paramS
  array unset paramU
  foreach k [array names paramU_bk $paramU_bk(i),*] {
    set kList [split $k ","]
    set sk "[lindex $kList 1],[lindex $kList 2]"
    set paramU($sk) $paramU_bk($k)
  }
  foreach k [array names paramS_bk $paramS_bk(i),*] {
    set kList [split $k ","]
    set sk "[lindex $kList 1],[lindex $kList 2]"
    set paramS($sk) $paramS_bk($k)
  }
  set v(paramChanged) 1
  Redraw param
}

#---------------------------------------------------
# パラメータ配列のリドゥ
#
proc redoParam {} {
  global v paramU paramS paramU_bk paramS_bk paramUsize

  if {$paramS_bk(i) >= $paramS_bk(N)} return
  ;# アンドゥポイントの内容と同じかどうかチェック
  set diff 0
  for {set r 1} {$r < $paramUsize} {incr r} {
    for {set c 0} {$c < 7} {incr c} {
      if {[array names paramU    $r,$c] == "" && \
          [array names paramU_bk $paramU_bk(i),$r,$c] == ""} continue

      if {[array names paramU    $r,$c] != "" && \
          [array names paramU_bk $paramU_bk(i),$r,$c] != ""} {
        if {$paramU($r,$c) != $paramU_bk($paramU_bk(i),$r,$c)} {
          set diff 1
          break
        }
      } else {
        set diff 1
        break
      }
    }
  }
  if {$diff == 1} return   ;# 内容が違うならここで終了

  incr paramS_bk(i)
  incr paramU_bk(i)
  array unset paramS
  array unset paramU
  foreach k [array names paramU_bk $paramU_bk(i),*] {
    set kList [split $k ","]
    set sk "[lindex $kList 1],[lindex $kList 2]"
    set paramU($sk) $paramU_bk($k)
  }
  foreach k [array names paramS_bk $paramS_bk(i),*] {
    set kList [split $k ","]
    set sk "[lindex $kList 1],[lindex $kList 2]"
    set paramS($sk) $paramS_bk($k)
  }
  set v(paramChanged) 1
  Redraw param
}

#---------------------------------------------------
# 一覧表の検索窓
#
proc searchParam {} {
  global v searchWindow t

  ;# 二重起動を防止
  if [winfo exists $searchWindow] {
    raise $searchWindow.f.e
    focus $searchWindow.f.e
    return
  }
  toplevel $searchWindow
  wm title $searchWindow $t(searchParam,title)
  bind $searchWindow <Escape> "destroy $searchWindow"

  set f [frame $searchWindow.f]
  entry  $f.e  -textvar v(keyword) -wi 30
  button $f.bs -text $t(searchParam,search) -command {doSearchParam $v(sdirection)}
  button $f.bc -text $t(.confm.c) -command {destroy $searchWindow}
  set f2 [labelframe $searchWindow.f2 -relief groove]
  radiobutton $f2.rup   -variable v(sdirection) -value 0 \
    -text $t(searchParam,rup)
  radiobutton $f2.rdown -variable v(sdirection) -value 1 \
    -text $t(searchParam,rdown)
  set f3 [labelframe $searchWindow.f3 -relief groove]
  radiobutton $f3.rMatch1 -variable v(sMatch) -value full \
    -text $t(searchParam,rMatch1)
  radiobutton $f3.rMatch2 -variable v(sMatch) -value sub \
    -text $t(searchParam,rMatch2)
  pack $f.e $f.bs $f.bc        -side left -anchor nw
  pack $f2.rup $f2.rdown       -side top  -anchor nw
  pack $f3.rMatch1 $f3.rMatch2 -side top  -anchor ne
  pack $f -anchor nw
  pack $f2 $f3 -anchor nw -side left

  bind $searchWindow <Return> {doSearchParam $v(sdirection)}
  raise $f.e
  focus $f.e
}

#---------------------------------------------------
# 一覧表の検索
#
proc doSearchParam {{direction 1}} {
  if $direction {
    doSearchParamNext
  } else {
    doSearchParamPrev
  }
}

#---------------------------------------------------
# 一覧表の検索(先頭方向)
#
proc doSearchParamPrev {} {
  global v paramU paramUsize searchWindow t

  if {$paramUsize <= 1} return

  ;# 現在のアクティブセルの前のセルを求める
  set sel [.entpwindow.t index active]
  if {$sel == ""} return
  set rs [lindex [split $sel ","] 0]
  set cs [lindex [split $sel ","] 1]
  if {$cs > 1} {  ;# ここを $cs > 0にすると前に戻れなくなるので×。
    incr cs -1
  } else {
    set cs 6
    incr rs -1
  }

  ;# 現在のアクティブセルの前から先頭までを検索
  for {set r $rs} {$r > 0} {incr r -1} {
    for {set c $cs} {$c >= 0} {incr c -1} {
      if {[array get paramU "$r,$c"] != ""} {
        if {$v(sMatch) == "full" && $v(keyword) == $paramU($r,$c) || \
            $v(sMatch) == "sub"  && [string first $v(keyword) $paramU($r,$c)] >= 0} {
          if {$c == 0} {incr c}
          focus .entpwindow.t
          jumpList .entpwindow.t 0 0 0 $r $c
          return
        }
      }
    }
    set cs 6
  }
  ;# 末尾から現在のアクティブセルあたりまでを検索
  for {set r [expr $paramUsize - 1]} {$r >= $rs} {incr r -1} {
    for {set c $cs} {$c >= 0} {incr c -1} {
      if {[array get paramU "$r,$c"] != ""} {
        if {$v(sMatch) == "full" && $v(keyword) == $paramU($r,$c) || \
            $v(sMatch) == "sub"  && [string first $v(keyword) $paramU($r,$c)] >= 0} {
          if {$c == 0} {incr c}
          focus .entpwindow.t
          jumpList .entpwindow.t 0 0 0 $r $c
          return
        }
      }
    }
    set cs 6
  }
  ;# 見つからなかった場合
  tk_messageBox -title $t(searchParam,doneTitle) -icon warning \
      -message [eval format $t(searchParam,doneMsg)]
  if [winfo exists $searchWindow] {
    focus $searchWindow.f.e
  } else {
    focus .entpwindow.t
  }
}

#---------------------------------------------------
# 一覧表の検索(末尾方向)
#
proc doSearchParamNext {} {
  global v paramU paramUsize searchWindow t

  if {$paramUsize <= 1} return

  ;# 現在のアクティブセルの次のセルを求める
  set sel [.entpwindow.t index active]
  if {$sel == ""} return
  set rs [lindex [split $sel ","] 0]
  set cs [lindex [split $sel ","] 1]
  if {$cs < 6} {
    incr cs
  } else {
    set cs 0
    incr rs
  }

  ;# 現在のアクティブセルの次から末尾までを検索
  for {set r $rs} {$r < $paramUsize} {incr r} {
    for {set c $cs} {$c < 7} {incr c} {
      if {[array get paramU "$r,$c"] != ""} {
        if {$v(sMatch) == "full" && $v(keyword) == $paramU($r,$c) || \
            $v(sMatch) == "sub"  && [string first $v(keyword) $paramU($r,$c)] >= 0} {
          if {$c == 0} {incr c}
          focus .entpwindow.t
          jumpList .entpwindow.t 0 0 0 $r $c
          return
        }
      }
    }
    set cs 0
  }
  ;# 先頭から現在のアクティブセルまでを検索
  for {set r 1} {$r <= $rs} {incr r} {
    for {set c $cs} {$c < 7} {incr c} {
      if {[array get paramU "$r,$c"] != ""} {
        if {$v(sMatch) == "full" && $v(keyword) == $paramU($r,$c) || \
            $v(sMatch) == "sub"  && [string first $v(keyword) $paramU($r,$c)] >= 0} {
          if {$c == 0} {incr c}
          focus .entpwindow.t
          jumpList .entpwindow.t 0 0 0 $r $c
          return
        }
      }
    }
    set cs 0
  }
  ;# 見つからなかった場合
  tk_messageBox -title $t(searchParam,doneTitle) -icon warning \
      -message [eval format $t(searchParam,doneMsg)]
  if [winfo exists $searchWindow] {
    focus $searchWindow.f.e
  } else {
    focus .entpwindow.t
  }
}

#---------------------------------------------------
# DC成分除去
#
proc removeDC {} {
  global snd t v
  set flt [snack::filter iir -numerator "0.99 -0.99" -denominator "1 -0.99"] 
  snd filter $flt -continuedrain 0
  set v(sndLength) [snd length -unit SECONDS]
}

#---------------------------------------------------
# 波形を横方向に拡大縮小(ctrl+マウスホイール)
# mode=1...拡大, mode=0...縮小
#
proc changeWidth {mode {x -1}} {
  global v t c wscrl

  if {$v(sndLength) <= 0} return

  if {$x < 0} {
    set x [expr $v(cWidth) / 2]
  }
  set sec [point2sec $x]
  set v(showWhole) 0

  if $mode {
    set v(wavepps) [expr $v(wavepps) * 1.5]
  } else {
    set v(wavepps) [expr $v(wavepps) / 1.5]
  }
  # 描画幅がキャンバスより小さくならないようにする
  if {[expr $v(wavepps) * $v(sndLength)] < $v(cWidth)} {
    set v(wavepps) [expr $v(cWidth) / $v(sndLength)]
  }
  # 描画幅がある程度よりおおきくならないようにする
  if {[expr int($v(sndLength) * $v(wavepps))] > 48000} {
    set v(wavepps) [expr 48000 / $v(sndLength)]
  }
  Redraw scale

  $c xview moveto [expr double($sec - double($x) / $v(wavepps)) / $v(sndLength)]

#  if $mode {
#    incr v(cWidth) +40
#  } elseif {$v(cWidth) <= $v(cWidthMin)} {
#    set v(cWidth) $v(cWidthMin)
#  } else {
#    incr v(cWidth) -40
#  }
#  Redraw scale
}


#---------------------------------------------------
# 時間スケールを拡大縮小する
# zoom...拡大率。100=wav全体表示、0=常にwav全体表示、負=最大拡大率
#
proc zoomX {zoom x} {
  global v t c wscrl

  if {$v(sndLength) <= 0} return
  set sec [point2sec $x]

  if {$zoom < 0} {
    set v(wavepps) [expr 48000 / $v(sndLength)]       ;# 最大拡大率にする
    set v(showWhole) 0
  } elseif {$zoom == 0} {
    set v(wavepps) [expr $v(cWidth) / $v(sndLength)]  ;# wav全体を表示
    set v(showWhole) 1                       ;# 常にwav全体を表示
  } elseif {$zoom <= 100} {
    set v(wavepps) [expr $v(cWidth) / $v(sndLength)]  ;# wav全体を表示
    set v(showWhole) 0
  } else {
    set v(wavepps) [expr $v(cWidth) / $v(sndLength) * $zoom / 100]
    set v(showWhole) 0
  }

  if {[expr int($v(sndLength) * $v(wavepps))] > 48000} {
    set v(wavepps) [expr 48000 / $v(sndLength)]       ;# 最大拡大率にする
  }

  Redraw scale
  $c xview moveto [expr double($sec - double($x) / $v(wavepps)) / $v(sndLength)]
}

#---------------------------------------------------
# 音名リストボックスを横方向に拡大縮小(ctrl+マウスホイール)
# mode=1...拡大, mode=0...縮小
#
proc changeRecListWidth {mode} {
  global rec t
  set width [$rec cget -width]
  if $mode {
    $rec configure -width [expr $width +1]   ;# 拡大
  } elseif {$width > 5} {
    $rec configure -width [expr $width -1]   ;# 縮小
  }
}

#---------------------------------------------------
# 引数で指定した単位の値をサンプル単位に変換する
#
proc val2samp {val from sr} {
  switch $from {
    MSEC -
    msec { ;# msec → サンプル単位に変換
      return [expr int($val / 1000.0 * $sr)]
    }
    SEC  -
    sec  { ;# sec → サンプル単位に変換
      return [expr int($val * $sr)]
    }
    default { ;# そのまま返す(念のため整数化する)
      return int($val);
    }
  }
}

#---------------------------------------------------
# 録音開始
#
proc aRecStart {} {
  global snd v bgmParam t
  if {$v(rec) == 0 || $bgmParam(autoRecStatus) == 0} return   ;# 収録モードでないなら終了
  snd record
  set v(recStatus) 1
}

#---------------------------------------------------
# 録音終了
#
proc aRecStop {} {
  global snd v t
  if {$v(rec) == 0} return   ;# 収録モードでないなら終了
  snd stop
  if $v(removeDC) removeDC
  set v(sndLength) [snd length -unit SECONDS]
;#  Redraw all
}

proc autoRecStop {} {
  global bgm bgmParam v t
  set bgmParam(autoRecStatus) 0
  bgm stop   ;# ←本当はこれで止まって欲しいけど止まってくれない。。
  .msg.msg configure -fg black
  set v(msg) [eval format $t(autoRecStop,doneMsg)]
  aRecStop
}

#---------------------------------------------------
# メトロノーム再生/停止の切替
#
proc toggleMetroPlay {} {
  global v metro t

  if $v(playMetroStatus) {
    set v(msg) $t(toggleMetroPlay,stopMsg)
    metro stop  ;# 本当はここで止まって欲しいが止まらない
    set v(playMetroStatus) 0
  } else {
    ;# テンポのチェック
    if {$v(tempo) < 50 || $v(tempo) > 200} {
      tk_messageBox -title $t(toggleMetroPlay,errTitle) -icon error \
        -message [eval format $t(toggleMetroPlay,errMsg)]
      return
    }
    if ![file exists $v(clickWav)] {
      tk_messageBox -title $t(toggleMetroPlay,errTitle) -icon error \
        -message [eval format $t(toggleMetroPlay,errMsg2)]
      return
    }
    metro read $v(clickWav)
    set v(playMetroStatus) 1
    loopPlay metro [expr int($v(tempoMSec) / 1000.0 * [metro cget -rate])]
    set v(msg) $t(toggleMetroPlay,playMsg)
  }
}

#---------------------------------------------------
# wavをループ再生する
#
proc loopPlay {s end} {
  global v t
  if $v(playMetroStatus) {
    $s play -start 0 -end $end -command "loopPlay $s $end"
  }
}

#---------------------------------------------------
# 音叉再生/停止の切替
#
proc toggleOnsaPlay {} {
  global v f0 onsa t

  if $v(playOnsaStatus) {
    set v(msg) $t(toggleOnsaPlay,stopMsg)
    onsa stop
    set v(playOnsaStatus) 0
  } else {
    set v(msg) $t(toggleOnsaPlay,playMsg)
    set v(playOnsaStatus) 1
    playSin [tone2freq $f0(guideTone)$f0(guideOctave)] \
      $f0(guideVol) -1
  }
}

#---------------------------------------------------
# 再生/停止の切替
#
proc togglePlay {{start 0} {end -1}} {
  global v snd t

  if $v(playStatus) {
    snd stop
    set v(playStatus) 0
    set v(msg) $t(togglePlay,stopMsg)
  } else {
    set v(msg) $t(togglePlay,playMsg)
    set v(playStatus) 1
    snd play -start $start -end $end -command {
      set v(playStatus) 0       ;# 再生終了したときの処理
      set v(msg) $t(togglePlay,stopMsg)
    }
  }
}

#---------------------------------------------------
# 左右ブランク間を再生
#
proc playFromStoE {} {
  global paramS v t
  set start  0
  set end   -1
  set ls $v(listSeq)
  if {[array get paramS "$ls,S"] != ""} {
    set start [expr int($v(sampleRate) * $paramS($ls,S))]
  }
  if {[array get paramS "$ls,E"] != ""} {
    set end   [expr int($v(sampleRate) * $paramS($ls,E))]
  }
  togglePlay $start $end
}

#---------------------------------------------------
# 範囲指定再生
#
proc playRange {fnum} {
  global paramS v t
  set ls $v(listSeq)

  set start 0
  set sSeq [string range  "$v(setPlayRange,$fnum,start)" 0 0] ;# 0..6=ファイル先頭,S,O,P,C,E,末尾
  if {$sSeq != 0 && $sSeq != 6} {
    set c ""
    switch $sSeq {
      1 { set c S }
      2 { set c O }
      3 { set c P }
      4 { set c C }
      5 { set c E }
    }
    if {[array get paramS "$ls,$c"] != ""} {
      set start [expr int($v(sampleRate) * $paramS($ls,$c))]
    }
  }

  set end -1
  set eSeq [string range  "$v(setPlayRange,$fnum,end)"   0 0] ;# 0..6=ファイル先頭,S,O,P,C,E,末尾
  if {$eSeq != 0 && $eSeq != 6} {
    set c ""
    switch $eSeq {
      1 { set c S }
      2 { set c O }
      3 { set c P }
      4 { set c C }
      5 { set c E }
    }
    if {[array get paramS "$ls,$c"] != ""} {
      set end [expr int($v(sampleRate) * $paramS($ls,$c))]
    }
  }
  togglePlay $start $end
}

#---------------------------------------------------
# 範囲指定再生の設定
#
proc setPlayRange {} {
  global v playRangeWindow t

  if [isExist $playRangeWindow] return ;# 二重起動を防止
  toplevel $playRangeWindow
  wm title $playRangeWindow $t(setPlayRange,title)
  bind $playRangeWindow <Escape> "destroy $playRangeWindow"

  array unset lf
  for {set i 1} {$i <= 5} {incr i} {
    set lf($i) [labelframe $playRangeWindow.lf$i -text "Ctrl+F$i" \
      -relief groove -padx 5 -pady 5]

    label $lf($i).ls -text "$t(setPlayRange,start)"
    label $lf($i).le -text "$t(setPlayRange,end)"
    ;# 開始位置の設定
    tk_optionMenu $lf($i).ss v(setPlayRange,$i,start) \
      "0 $t(setPlayRange,head)" "1 $t(initParamU,1)" "2 $t(initParamU,2)" \
      "3 $t(initParamU,3)"      "4 $t(initParamU,4)" "5 $t(initParamU,5)"
    ;# 終了位置の設定
    tk_optionMenu $lf($i).se v(setPlayRange,$i,end) \
      "1 $t(initParamU,1)" "2 $t(initParamU,2)" "3 $t(initParamU,3)"    \
      "4 $t(initParamU,4)" "5 $t(initParamU,5)" "6 $t(setPlayRange,tail)"

    grid $lf($i).ls -row 0 -column 0
    grid $lf($i).le -row 1 -column 0
    grid $lf($i).ss -row 0 -column 1
    grid $lf($i).se -row 1 -column 1
  }

  grid $lf(1) -row 0 -column 0
  grid $lf(2) -row 0 -column 1
  grid $lf(3) -row 0 -column 2
  grid $lf(4) -row 1 -column 0
  grid $lf(5) -row 1 -column 1

  raise $playRangeWindow
  focus $playRangeWindow
}

#---------------------------------------------------
# 発声タイミング補正モードON/OFFの切替
#
proc timingAdjMode {} {
  global v t

  if $v(timingAdjMode) {
    tk_messageBox -message [eval format $t(timingAdjMode,startMsg)] \
      -icon info
    set v(msg)  $t(timingAdjMode,on)
  } else {
    tk_messageBox -message [eval format $t(timingAdjMode,doneMsg)] \
      -icon info
    set v(msg)  $t(timingAdjMode,off)
  }
}

#---------------------------------------------------
# 波形表示/非表示の切替
#
proc toggleWave {} {
  global v t

  ;#if [snack::audio active] return
  if $v(showWave) {
      set v(waveh) $v(wavehbackup)
  } else {
      set v(wavehbackup) $v(waveh)
      set v(waveh) 0
  }
  Redraw wave
}

#---------------------------------------------------
# スペクトル表示/非表示の切替
#
proc toggleSpec {} {
  global v t

  ;#if [snack::audio active] return
  if $v(showSpec) {
      set v(spech) $v(spechbackup)
  } else {
      set v(spechbackup) $v(spech)
      set v(spech) 0
  }
  Redraw spec
}

#---------------------------------------------------
# パワー表示/非表示の切替
#
proc togglePow {} {
  global v t

  ;#if [snack::audio active] return
  if $v(showpow) {
      set v(powh) $v(powhbackup)
  } else {
      set v(powhbackup) $v(powh)
      set v(powh) 0
  }
  Redraw pow
}

#---------------------------------------------------
# F0表示/非表示の切替
#
proc toggleF0 {} {
  global v t

  ;#if [snack::audio active] return
  if $v(showf0) {
      set v(f0h) $v(f0hbackup)
  } else {
      set v(f0hbackup) $v(f0h)
      set v(f0h) 0
  }
  Redraw f0
}

#---------------------------------------------------
# UTAU用原音パラメータ表示/非表示の切替
#
proc toggleParam {} {
  global v t

  RedrawParam
}

#---------------------------------------------------
# パワーパネルの縦軸表示
# snack の unix/snack.tcl の frequencyAxis を改造
proc powerAxis {canvas x y width height args} {
  # 引数処理
  array set a [list \
    -tags snack_y_axis -font {Helvetica 8} -pwMax 100 \
    -fill black -draw0 0 -pwMin 0]
  array set a $args

  if {$height <= 0} return
  if {$a(-pwMax) <= $a(-pwMin)} return

  ;# ticklist...目盛りの間隔の候補
  set ticklist [list 1 2 5 10 20 50 100 200 500]
  set npt 1   ;# npt...目盛りの値の間隔
  ;# dy...目盛りを描画する間隔(y座標)
  set dy [expr {double($height * $npt) / ($a(-pwMax) - $a(-pwMin))}]

  while {$dy < [font metrics $a(-font) -linespace]} {
    foreach elem $ticklist {
      if {$elem <= $npt} {
        continue
      }
      set npt $elem
      break
    }
    set dy [expr {double($height * $npt) / ($a(-pwMax) - $a(-pwMin))}]
  }
  set hztext dB

  if $a(-draw0) {
    set i0 0
    set j0 0
  } else {
    set i0 $dy
    set j0 1
  }

  if {$a(-pwMin) != 0} {
    set j0 [expr int($a(-pwMin) / $npt) + 1]
  }

  for {set i $i0; set j $j0} {$i < $height} {set i [expr {$i+$dy}]; incr j} {
    set yc [expr {$height + $y - $i}]  ;# 描画するy座標

    if {$npt < 1000} {
      set tm [expr {$j * $npt}]
    } else {
      set tm [expr {$j * $npt / 1000}]
    }
    if {$yc > [expr {8 + $y}]} {
      if {[expr {$yc - [font metrics $a(-font) -ascent]}] > \
          [expr {$y + [font metrics $a(-font) -linespace]}] ||
          [font measure $a(-font) $hztext]  < \
          [expr {$width - 8 - [font measure $a(-font) $tm]}]} {
        $canvas create text [expr {$x +$width - 8}] [expr {$yc-2}]\
          -text $tm -fill $a(-fill)\
          -font $a(-font) -anchor e -tags $a(-tags)
      }
      $canvas create line [expr {$x + $width - 5}] $yc \
        [expr {$x + $width}]\
        $yc -tags $a(-tags) -fill $a(-fill)
    }
  }
  $canvas create text [expr {$x + 2}] [expr {$y + 1}] -text $hztext \
    -font $a(-font) -anchor nw -tags $a(-tags) -fill $a(-fill)

  return $npt
}

#---------------------------------------------------
# F0パネルの縦軸表示
# snack の unix/snack.tcl の frequencyAxis を改造
proc f0Axis {canvas x y width height args} {
  # 引数処理
  array set a [list \
    -tags snack_y_axis -font {Helvetica 8} -f0Max 100 \
    -fill black -draw0 0 -f0Min 0 -unit Hz]
  array set a $args

  if {$height <= 0} return
  if {$a(-f0Max) <= $a(-f0Min)} return

  ;# ticklist...目盛りの間隔の候補
  set ticklist [list 1 2 5 10 20 50 100 200 500]
  set npt 1   ;# npt...目盛りの値の間隔
  ;# dy...目盛りを描画する間隔(y座標)
  set dy [expr {double($height * $npt) / ($a(-f0Max) - $a(-f0Min))}]

  while {$dy < [font metrics $a(-font) -linespace]} {
    foreach elem $ticklist {
      if {$elem <= $npt} {
        continue
      }
      set npt $elem
      break
    }
    set dy [expr {double($height * $npt) / ($a(-f0Max) - $a(-f0Min))}]
  }
  set hztext $a(-unit)
  if {$hztext == "semitone"} {set hztext st} ;# 表示を短縮

  if $a(-draw0) {
    set i0 0
    set j0 0
  } else {
    set i0 $dy
    set j0 1
  }

  if {$a(-f0Min) != 0} {
    set j0 [expr int($a(-f0Min) / $npt) + 1]
  }

  ;# j=描画する目盛りの番号, i=描画座標に関する変数
  for {set i $i0; set j $j0} {$i < $height} {set i [expr {$i+$dy}]; incr j} {
    set yc [expr {$height + $y - $i}]  ;# 描画するy座標

    if {$npt < 1000} {
      set tm [expr {$j * $npt}]
    } else {
      set tm [expr {$j * $npt / 1000}]
    }
    if {$yc > [expr {8 + $y}]} {
      if {[expr {$yc - [font metrics $a(-font) -ascent]}] > \
          [expr {$y + [font metrics $a(-font) -linespace]}] ||
          [font measure $a(-font) $hztext]  < \
          [expr {$width - 8 - [font measure $a(-font) $tm]}]} {
        $canvas create text [expr {$x +$width - 8}] [expr {$yc-2}]\
          -text $tm -fill $a(-fill)\
          -font $a(-font) -anchor e -tags $a(-tags)
      }
      $canvas create line [expr {$x + $width - 5}] $yc \
        [expr {$x + $width}]\
        $yc -tags $a(-tags) -fill $a(-fill)
    }
  }
  $canvas create text [expr {$x + 2}] [expr {$y + 1}] -text $hztext \
    -font $a(-font) -anchor nw -tags $a(-tags) -fill $a(-fill)

  return $npt
}

#---------------------------------------------------
# 色選択
#
proc chooseColor {w key initcolor} {
  global v t
  set ctmp [tk_chooseColor -initialcolor $initcolor -title $t(chooseColor,title)]
  if {$ctmp != ""} {
    set v($key) $ctmp
    $w configure -bg $v($key)
  }
}

#---------------------------------------------------
# 音名に対応する周波数を返す
#
proc tone2freq {tone} {
  global v t
  for {set i 0} {$i < [llength $v(sinNote)]} {incr i} {
    if {$tone == [lindex $v(sinNote) $i]} break
  }
  return [lindex $v(sinScale) $i]
}

#---------------------------------------------------
# 波形色設定
#
proc setColor {w key msg} {
  global v t

  set ic $v($key)
  pack [frame $w.$key] -anchor nw
  label  $w.$key.l  -text $msg -width 20 -anchor nw
  label  $w.$key.l2 -textvar v($key) -width 7 -anchor nw -bg $v($key)
  button $w.$key.b  -text $t(setColor,selColor) -command "chooseColor $w.$key.l2 $key $ic"
  pack $w.$key.l $w.$key.l2 $w.$key.b -side left
}

#---------------------------------------------------
# 1Hzに対する周波数比をセミトーンにする
#
proc hz2semitone {hz} {
  return [expr log($hz) / log(2) * 12.0]
}


#---------------------------------------------------
# 項目名：[エントリー]のフレームを作って配置する(power用)
#
proc packEntryPower {wname text key} {
  global power t
  pack [frame $wname] -anchor w
  label $wname.lfl -text $text -width 20 -anchor w
  entry $wname.efl -textvar power($key) -wi 6
  pack $wname.lfl $wname.efl -side left
}

#---------------------------------------------------
# 項目名：[エントリー]のフレームを作って配置する(f0用)
#
proc packEntryF0 {wname text key} {
  global f0 t
  pack [frame $wname] -anchor w
  label $wname.lfl -text $text -width 20 -anchor w
  entry $wname.efl -textvar f0($key) -wi 6
  pack $wname.lfl $wname.efl -side left
}

#---------------------------------------------------
# 音名の選択メニューをpackしたフレームを生成
#
proc packToneList {w text toneKey octaveKey freqKey width vol} {
  global f0 v t
  pack [frame $w] -fill x
  # 項目名ラベル
  label $w.l -text $text -width $width -anchor w
  # 音名選択
  eval tk_optionMenu $w.t f0($toneKey) $v(toneList)
  # オクターブ選択
  set ss {}
  for {set i $v(sinScaleMin)} {$i <= $v(sinScaleMax)} {incr i} {
    lappend ss $i
  }
  eval tk_optionMenu $w.o f0($octaveKey) $ss
  # 試聴ボタン
  button $w.play -text $t(packToneList,play) -bitmap snackPlay -command \
    "playSin \[tone2freq \$f0($toneKey)\$f0($octaveKey)\] \$f0($vol) \$v(sampleRate)"
  button $w.togglePlay -text $t(packToneList,repeat) -command {
    toggleOnsaPlay
  }
  # 音に対応する周波数を表示するラベル
  label $w.$freqKey -textvar f0($freqKey) -width 3 -anchor e
  label $w.unit -text "Hz"
  pack $w.l $w.t $w.o $w.play $w.togglePlay $w.$freqKey $w.unit -side left
}

#---------------------------------------------------
#   入出力デバイスやバッファサイズを初期化する
#
proc audioSettings {} {
  global dev snd bgm t

  set dev(in)   [encoding convertfrom [lindex [snack::audio inputDevices]  0]]
  set dev(out)  [encoding convertfrom [lindex [snack::audio outputDevices] 0]]
  set dev(ingain)    [snack::audio record_gain]
  set dev(outgain)   [snack::audio play_gain]
  set dev(latency)   [snack::audio playLatency]
  set dev(sndBuffer) [snd cget -buffersize]
  set dev(bgmBuffer) [bgm cget -buffersize]
  # snack::audio selectInput $dev(in) ;# 漢字コード未対応
}

#---------------------------------------------------
#   入出力デバイスの設定窓の値をデバイスに反映させる
#
proc setIODevice {} {
  global dev snd bgm t
  ;# dev(in),dev(out)にはメニュー表示のため漢字コードをsjis→utf-8に
  ;# 変換した文字列を入れている。デバイス設定時には元の漢字コード文字列で
  ;# 指定しないとエラーになる様子なので以下のようなコードで対応している
  foreach dname [snack::audio inputDevices] {
    if {$dev(in) == [encoding convertfrom $dname]} {
      snack::audio selectInput  $dname
      break
    }
  }
  foreach dname [snack::audio outputDevices] {
    if {$dev(out) == [encoding convertfrom $dname]} {
      snack::audio selectOutput $dname
      break
    }
  }
  snack::audio record_gain  $dev(ingain)
  snack::audio play_gain    $dev(outgain)
  snack::audio playLatency  $dev(latency)
  snd configure -buffersize $dev(sndBuffer)
  bgm configure -buffersize $dev(bgmBuffer)
}

#---------------------------------------------------
#   現在の設定を保存する
#
proc saveSettings {} {
  global v f0 power startup dev uttTiming genParam bgmParam estimate t
  # ↑保存する配列を増やしたら、ここに追加すると共にdoReadInitFileのglobalにも追加

  set fn [tk_getSaveFile -initialfile $startup(initFile) \
            -title $t(saveSettings,title) -defaultextension "tcl" ]
  if {$fn == ""} return

  set aList {v f0 power startup dev uttTiming genParam bgmParam estimate}

  ;# 保存ファイルを開く
  if [catch {open $fn w} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }

  foreach aName $aList {
    set sList [array get $aName]
    foreach {key value} $sList {
      if {$aName == "v"} {
        if {   $key != "paramChanged" 
            && $key != "msg"
            && $key != "appname"
            && $key != "version"} {
          puts $fp [format "set %s(%s)\t\t{%s}" $aName $key $value]
        }
      } else {
        puts $fp [format "set %s(%s)\t\t{%s}" $aName $key $value]
      }
    }
  }
  close $fp
}

#---------------------------------------------------
#   指定した窓が起動済みかチェック。起動済みならフォーカスする。
#
proc isExist {w} {
  if [winfo exists $w] {
    raise $w
    focus $w
    return 1
  } else {
    return 0
  }
}

#---------------------------------------------------
#   入出力デバイスやバッファサイズを設定する窓
#
proc ioSettings {} {
  global ioswindow dev dev_bk snd bgm t

  if [isExist $ioswindow] return ;# 二重起動を防止
  toplevel $ioswindow
  wm title $ioswindow $t(ioSettings,title)
  bind $ioswindow <Escape> "destroy $ioswindow"

  ;# ゲイン、レイテンシの最新状況を取得
  set dev(ingain)  [snack::audio record_gain]
  set dev(outgain) [snack::audio play_gain]
  set dev(latency) [snack::audio playLatency]
  set dev(sndBuffer) [snd cget -buffersize]
  set dev(bgmBuffer) [bgm cget -buffersize]

  array set dev_bk [array get dev]     ;# パラメータバックアップ

  ;# 入力デバイスの選択
  set devList {}
  foreach d [snack::audio inputDevices] {
    set d [encoding convertfrom $d]
    lappend devList "$d"
    # if {[string length $d] == [string bytelength $d]} {  ;# 英語デバイスのみ登録
    #  lappend devList "$d"
    # }
  }
  set f1 [frame $ioswindow.f1]
  label $f1.l -text $t(ioSettings,inDev) -width 12 -anchor w
  eval tk_optionMenu $f1.in dev(in) $devList
  pack $f1.l $f1.in -side left
  pack $f1 -anchor w

  ;# 出力デバイスの選択
  set devList {}
  foreach d [snack::audio outputDevices] {
    set d [encoding convertfrom $d]
    lappend devList "$d"
    #if {[string length $d] == [string bytelength $d]} {  ;# 英語デバイスのみ登録
    #  lappend devList "$d"
    #}
  }
  set f2 [frame $ioswindow.f2]
  label $f2.l -text $t(ioSettings,outDev) -width 12 -anchor w
  eval tk_optionMenu $f2.out dev(out) $devList
  pack $f2.l $f2.out -side left
  pack $f2 -anchor w

  ;# 入力ゲインの指定
  set f3 [frame $ioswindow.f3]
  label $f3.l -text $t(ioSettings,inGain) -width 28 -anchor w
  entry $f3.e -textvar dev(ingain) -wi 6
  scale $f3.s -variable dev(ingain) -orient horiz \
    -from 0 -to 100 -res 1 -showvalue 0
  pack $f3.l $f3.e $f3.s -side left
  pack $f3 -anchor w

  ;# 出力ゲインの指定
  set f4 [frame $ioswindow.f4]
  label $f4.l -text $t(ioSettings,outGain) -width 28 -anchor w
  entry $f4.e -textvar dev(outgain) -wi 6
  scale $f4.s -variable dev(outgain) -orient horiz \
    -from 0 -to 100 -res 1 -showvalue 0
  pack $f4.l $f4.e $f4.s -side left
  pack $f4 -anchor w

  ;# レイテンシの指定
  set f5 [frame $ioswindow.f5]
  label $f5.l -text $t(ioSettings,latency) -width 28 -anchor w
  entry $f5.e -textvar dev(latency) -wi 6
  label $f5.u -text "(msec)"
  pack $f5.l $f5.e $f5.u -side left
  pack $f5 -anchor w

  ;# 収録音のバッファサイズの指定
  set f6 [frame $ioswindow.f6]
  label $f6.l -text $t(ioSettings,sndBuffer) -width 28 -anchor w
  entry $f6.e -textvar dev(sndBuffer) -wi 6
  label $f6.u -text "(sample)"
  pack $f6.l $f6.e $f6.u -side left
  pack $f6 -anchor w

  ;# ガイドBGMのバッファサイズの指定
  set f7 [frame $ioswindow.f7]
  label $f7.l -text $t(ioSettings,bgmBuffer) -width 28 -anchor w
  entry $f7.e -textvar dev(bgmBuffer) -wi 6
  label $f7.u -text "(sample)"
  pack $f7.l $f7.e $f7.u -side left
  pack $f7 -anchor w

  ;# 決定ボタン
  set fb [frame $ioswindow.fb]
  button $fb.ok -text $t(.confm.ok) -wi 6 -command {
    setIODevice
    destroy $ioswindow
  }
  button $fb.ap -text $t(.confm.apply) -wi 6 -command {
    setIODevice
    array set dev_bk [array get dev]     ;# パラメータバックアップ
  }
  button $fb.cn -text $t(.confm.c) -wi 6 -command {
    array set dev [array get dev_bk]     ;# パラメータを以前の状態に戻す
    setIODevice
    destroy $ioswindow
  }
  pack $fb.ok $fb.ap $fb.cn -side left
  pack $fb -anchor w

  ;# 説明文
  set fm [frame $ioswindow.fm]
  label $fm.lm0  -fg red -text $t(ioSettings,comment0)
  label $fm.lm0b -fg red -text $t(ioSettings,comment0b)
  label $fm.lm1  -fg red -text $t(ioSettings,comment1)
  label $fm.lm2  -fg red -text $t(ioSettings,comment2)
  pack $fm.lm0 $fm.lm0b $fm.lm1 $fm.lm2 -anchor w -side top
  pack $fm -anchor w

  raise $ioswindow
  focus $ioswindow
}

#---------------------------------------------------
# UTAU原音パラメータを自動推定する外部ツールを起動
#
proc autoParamEstimation {} {
  global aepwindow v t

  if [isExist $aepwindow] return ;# 二重起動を防止

  toplevel $aepwindow
  wm title $aepwindow $t(autoParamEstimation,title)
  wm resizable $aepwindow 0 0
  bind $aepwindow <Escape> "destroy $aepwindow"

  set w [frame $aepwindow.al]
  pack $w

  set v(aepResult) "$v(saveDir)/oto-autoEstimation.ini";
  set v(aepParamFile) [file dirname $v(aepTool)]/inParam.txt
  set v(aepArg)    "-f $v(aepParamFile)"  ;# "\"$v(saveDir)\" \"$v(aepResult)\"";

  ;# 外部ツールの指定
  label $w.lpath -text $t(autoParamEstimation,aepTool)
  button $w.bpath -textvar v(aepTool) \
    -fg $v(fg) -bg $v(bg) -relief solid \
    -command {
      set fn [tk_getOpenFile -initialfile $v(aepTool) \
              -title $t(autoParamEstimation,selTitle) -defaultextension "exe" \
              -filetypes { {{exe file} {.exe}} {{All Files} {*}} }]
      if {$fn != ""} { set v(aepTool) $fn }
    }
  button $w.bsel -image snackOpen -highlightthickness 0 -fg $v(fg) -bg $v(bg) \
    -command {
      set fn [tk_getOpenFile -initialfile $v(aepTool) \
              -title $t(autoParamEstimation,selTitle) -defaultextension "exe" \
              -filetypes { {{exe file} {.exe}} {{All Files} {*}} }]
      if {$fn != ""} { set v(aepTool) $fn }
    }

  ;# 外部ツールの起動引数
  label $w.larg -text $t(autoParamEstimation,option)
  entry $w.earg -textvar v(aepArg)

  ;# 外部ツールの出力するファイル名
  label $w.lresult -text $t(autoParamEstimation,aepResult)
  entry $w.eresult -textvar v(aepResult)

  ;# ボタン
  set f [frame $w.b]
  button $f.run -text $t(.confm.run) -command {
    if $v(paramChanged) {
      set act [tk_dialog .confm $t(.confm) $t(.confm.delParam) \
        question 1 $t(.confm.yes) $t(.confm.no)]
      if {$act == 1} return
    }
    destroy $aepwindow
    tk_dialog .confm "info" $t(autoParamEstimation,runMsg) "" 0 OK
    makeAepParam $v(aepParamFile) "$v(saveDir)" "$v(aepResult)"
    eval exec "$v(aepTool) -- $v(aepArg)"
    tk_dialog .confm "info" $t(autoParamEstimation,resultMsg) "" 0 OK
    set paramFileTmp $v(paramFile)
    readParamFile $v(aepResult)
    set v(paramFile) $paramFileTmp
    resetDisplay
    setCellSelection
  }
  button $f.cancel -text $t(.confm.c) -command {
    destroy $aepwindow
  }
  grid $f.run $f.cancel

  ;# 配置
  grid $w.lpath              -row 0 -column 0
  grid $w.bpath   -sticky we -row 0 -column 1
  grid $w.bsel    -sticky w  -row 0 -column 2
  grid $w.larg               -row 1 -column 0
  grid $w.earg    -sticky we -row 1 -column 1 -columnspan 2
  grid $w.lresult            -row 2 -column 0
  grid $w.eresult -sticky we -row 2 -column 1 -columnspan 2
  grid $f                    -row 3 -column 0 -columnspan 3

  raise $aepwindow
  focus $aepwindow
}

#---------------------------------------------------
# パラメータ自動推定コマンドへ渡すパラメータファイルを作る
#
proc makeAepParam {paramFile saveDir aepResult} {
  if [catch {open $paramFile w} fp] { 
    tk_messageBox -message "error: can not open $paramFile" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  puts $fp "$saveDir"
  puts $fp "$aepResult"
  close $fp
}

#---------------------------------------------------
# 単独音のUTAU原音パラメータを推定する際の設定窓
#
proc estimateParam {} {
  global epwindow power estimate v t

  if [isExist $epwindow] return  ;# 二重起動を防止
  toplevel $epwindow
  wm title $epwindow $t(estimateParam,title)
  wm resizable $epwindow 0 0
  bind $epwindow <Escape> "destroy $epwindow"

  set w [frame $epwindow.al]
  pack $w
  set row 0

  ;# パワー抽出刻みの設定
  label $w.lfl -text $t(estimateParam,pFLen)
  entry $w.efl -textvar power(frameLength) \
    -validate all -validatecommand {
         if {[string is double %P]} {
           if {[string length %P] > 0 && %P > 0} {
             set tmp [sec2samp $power(uttLengthSec) %P]
             if {$tmp >= 0} {
               set power(uttLength) $tmp
             }
           }
           expr {1}
         } else {
           expr {0}
         }
    }
  label $w.lflu -text "(sec)"
  grid $w.lfl  -sticky w -row $row -column 0
  grid $w.efl  -sticky w -row $row -column 1
  grid $w.lflu -sticky w -row $row -column 2 -columnspan 2
  incr row

  ;# プリエンファシスの設定
  # frame $w.fem
  label $w.lem -text $t(estimateParam,preemph)
  entry $w.eem -textvar power(preemphasis)
  grid $w.lem  -sticky w -row $row -column 0
  grid $w.eem  -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# パワー抽出窓長の設定
  label $w.lwl -text $t(estimateParam,pWinLen)
  entry $w.ewlSec -textvar power(windowLength)
  label $w.lwlSec -text "(sec)"
  grid $w.lwl      -sticky w -row $row -column 0
  grid $w.ewlSec   -sticky w -row $row -column 1
  grid $w.lwlSec   -sticky w -row $row -column 2 -columnspan 2
  incr row

  ;# 窓の選択
  # frame $w.fwn
  label $w.lwn -text $t(estimateParam,pWinkind)
  tk_optionMenu $w.mwn power(window) \
    Hamming Hanning Bartlett Blackman Rectangle
  grid $w.lwn  -sticky w -row $row -column 0
  grid $w.mwn  -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 発話中とみなされるパワーの設定
  # frame $w.fhi
  label $w.lhi -text $t(estimateParam,pUttMin)
  entry $w.ehi -textvar power(uttHigh)
  label $w.lhiu -text "(db)"
  grid $w.lhi  -sticky w -row $row -column 0
  grid $w.ehi  -sticky w -row $row -column 1
  grid $w.lhiu -sticky w -row $row -column 2 -columnspan 2
  incr row

  ;# 発話中とみなされる時間長の設定
  # frame $w.ful
  label $w.lul -text $t(estimateParam,pUttMinTime)
  entry $w.eulSec -textvar power(uttLengthSec) \
    -validate all -validatecommand {
         set tmp [sec2samp %P $power(frameLength)]
         if {$tmp >= 0} {
           set power(uttLength) $tmp
           expr {1}
         } else {
           expr {0}
         }
    }
  label $w.lulSec -text "(sec) = "
  label $w.lulSamp -textvar power(uttLength)
  label $w.lulSampu -text "(sample)"
  grid $w.lul      -sticky w -row $row -column 0
  grid $w.eulSec   -sticky w -row $row -column 1
  grid $w.lulSec   -sticky w -row $row -column 2
  grid $w.lulSamp  -sticky w -row $row -column 3
  grid $w.lulSampu -sticky w -row $row -column 4
  incr row

  ;# 発声中に生じるパワーの揺らぎの大きさ設定
  # frame $w.fhi
  label $w.lkp -text $t(estimateParam,uttLen)
  entry $w.ekp -textvar power(uttKeep)
  label $w.lkpu -text "(db)"
  grid $w.lkp  -sticky w -row $row -column 0
  grid $w.ekp  -sticky w -row $row -column 1
  grid $w.lkpu -sticky w -row $row -column 2 -columnspan 2
  incr row

  ;# ポーズ中とみなされるパワーの設定
  # frame $w.flw
  label $w.llw -text $t(estimateParam,silMax)
  entry $w.elw -textvar power(uttLow)
  label $w.llwu -text "(db)"
  grid $w.llw  -sticky w -row $row -column 0
  grid $w.elw  -sticky w -row $row -column 1
  grid $w.llwu -sticky w -row $row -column 2 -columnspan 2
  incr row

  ;# 母音パワー最小値の設定（この値未満は子音部とみなす）
  label $w.lmv -text $t(estimateParam,vLow)
  entry $w.emvSec -textvar power(vLow)
  label $w.lmvSec -text "(db)"
  grid $w.lmv      -sticky w -row $row -column 0
  grid $w.emvSec   -sticky w -row $row -column 1
  grid $w.lmvSec   -sticky w -row $row -column 2
  incr row

  ;# ポーズとみなされる時間長の設定
  # frame $w.fsl
  label $w.lsl -text $t(estimateParam,silMinTime)
  entry $w.eslSec -textvar power(silLengthSec) \
    -validate all -validatecommand {
         set tmp [sec2samp %P $power(frameLength)]
         if {$tmp >= 0} {
           set power(silLength) $tmp
           expr {1}
         } else {
           expr {0}
         }
    }
  label $w.lslSec -text "(sec) = "
  label $w.lslSamp -textvar power(silLength)
  label $w.lslSampu -text "(sample)"
  grid $w.lsl      -sticky w -row $row -column 0
  grid $w.eslSec   -sticky w -row $row -column 1
  grid $w.lslSec   -sticky w -row $row -column 2
  grid $w.lslSamp  -sticky w -row $row -column 3
  grid $w.lslSampu -sticky w -row $row -column 4
  incr row

  ;# 子音長最小値の設定（子音部=0でUTAUがエラーになるのを回避するため）
  label $w.lmc -text $t(estimateParam,minC)
  entry $w.emcSec -textvar estimate(minC)
  label $w.lmcSec -text "(sec)"
  grid $w.lmc      -sticky w -row $row -column 0
  grid $w.emcSec   -sticky w -row $row -column 1
  grid $w.lmcSec   -sticky w -row $row -column 2
  incr row

  ;# 先行発声値に対するオーバーラップの割合の設定
  label $w.lol -text $t(estimateParam,ovl)
  entry $w.eol -textvar estimate(ovl)
  grid $w.lol -sticky w -row $row -column 0
  grid $w.eol -sticky w -row $row -column 1
  incr row

  ;# オーバーラップを求める対象の設定
  label $w.lop -text $t(estimateParam,ovlPattern)
  entry $w.eop -textvar estimate(ovlPattern)
  grid $w.lop -sticky w -row $row -column 0
  grid $w.eop -sticky w -row $row -column 1
  incr row

  ;# F0に関する説明文
  label $w.lf0 -text $t(estimateParam,f0)
  grid $w.lf0      -sticky w -row $row -column 0 -columnspan 3
  incr row

  ;# 推定対象の設定
  # frame $w.fem
  label $w.l$row -text $t(estimateParam,target)
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable estimate(S)
  label $w.fe$row.t -text $t(estimateParam,S)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.l$row  -sticky w -row $row -column 0
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  # frame $w.fem
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable estimate(C)
  label $w.fe$row.t -text $t(estimateParam,C)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  # frame $w.fem
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable estimate(E)
  label $w.fe$row.t -text $t(estimateParam,E)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  # frame $w.fem
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable estimate(P)
  label $w.fe$row.t -text $t(estimateParam,P)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# 推定対象の設定
  # frame $w.fem
  frame $w.fe$row
  checkbutton $w.fe$row.b -variable estimate(O)
  label $w.fe$row.t -text $t(estimateParam,O)
  pack $w.fe$row.b $w.fe$row.t -side left
  grid $w.fe$row -sticky w -row $row -column 1 -columnspan 3
  incr row

  ;# ボタンの設定
  # frame $w.fbt
  button $w.do -text $t(estimateParam,runAll) -command {
    if $v(paramChanged) {
      set act [tk_dialog .confm $t(.confm) $t(estimateParam,overWrite) \
        question 1 $t(.confm.ok) $t(.confm.c)]
      if {$act == 1} return
    }
    ;# もしエントリ欄が空欄だったなら0を代入
    if {[string length $power(silLengthSec)] == 0} {
      set power(silLengthSec) 0
    }
    if {[string length $power(uttLengthSec)] == 0} {
      set power(uttLengthSec) 0
    }

    if {$::tcl_platform(os) != "Darwin"} { grab set $epwindow }
    if {$v(appname) == "OREMO"}          { makeRecListFromDir 0 }
    doEstimateParam all
    if {$::tcl_platform(os) == "Darwin"} { deleteProgressWindow } ;# なぜかmacではdoEstimateParamの中で消せなかった
    if {$v(appname) == "OREMO"}          { saveParamFile }
    if {$::tcl_platform(os) != "Darwin"} { grab release $epwindow }
    destroy $epwindow
    if {$v(appname) != "OREMO"} {
      resetDisplay
      setCellSelection
    }
  }
  grid $w.do     -sticky we -row $row -column 0
  if {$v(appname) != "OREMO"} {
    button $w.do2 -text $t(estimateParam,runSel) -command {
      ;# もしエントリ欄が空欄だったなら0を代入
      if {[string length $power(silLengthSec)] == 0} {
        set power(silLengthSec) 0
      }
      if {[string length $power(uttLengthSec)] == 0} {
        set power(uttLengthSec) 0
      }

      grab set $epwindow     ;# 推定中は他の操作が出来ないようにする
      doEstimateParam sel
      grab release $epwindow ;# 推定中は他の操作が出来ないようにする
      # destroy $epwindow
      Redraw all
    }
    grid $w.do2    -sticky we -row $row -column 1
  }
  button $w.cancel -text $t(.confm.c) -command {destroy $epwindow}
  grid $w.cancel -sticky we -row $row -column 2
  incr row
}

#---------------------------------------------------
# UTAU原音パラメータの推定
#
proc doEstimateParam {{mode all}} {
  global power v paramS paramU paramUsize estimate t f0 ;# f0は他でも使っている

  set v(msg) $t(doEstimateParam,startMsg)

  snack::sound sWork
  set pattern [split $estimate(ovlPattern) " "]

  initProgressWindow

  ;# 推定する行番号のリストを作る
  set targetList {}
  if {$mode == "all"} {
    for {set i 1} {$i < $paramUsize} {incr i} {
      lappend targetList $i
    }
  } else {
    foreach pos [.entpwindow.t curselection] {
      set r [lindex [split $pos ","] 0]
      if {[lindex $targetList end] != $r} {
        lappend targetList $r
      }
    }
  }

  foreach i $targetList {
    ;# wavファイルを読む
    set fid $paramU($i,0)
    set filename $v(saveDir)/$fid.$v(ext)
    sWork read $filename

    ;# パワーを抽出する
    set pw [sWork power -framelength $power(frameLength) \
      -windowtype $power(window) -preemphasisfactor $power(preemphasis) \
      -windowlength [expr int($power(windowLength) * $v(sampleRate))] \
      -start 0 -end -1]

    ;# 初期値設定
    if {[array names paramS "$i,S"] == ""} {
      set paramS($i,S) 0
      set paramU($i,[kind2c S]) [sec2u $i S $paramS($i,S)]
    }
    if $estimate(C) { set paramS($i,C) $estimate(minC) }
    if $estimate(E) { set paramS($i,E) [sWork length -unit SECONDS] }
    set uttS 0           ;# 発声音量が十分な大きさになっているとみなされた位置
    set uttE [expr [llength $pw] - 1]     ;# 発声音量が減衰し始める位置

    if {[llength $pw] > 0} {
      ;# 左側の発話中確定点を探す
      set length 0   ;# 発話が連続しているサンプル数
      for {set j 0} {$j < [llength $pw]} {incr j} {
        if {[lindex $pw $j] >= $power(uttHigh)} {
          incr length
        } else {
          set length 0
        }
        if {$length >= [expr $power(uttLength) + 1]} {
          set uttS $j     ;# 現在位置を保存
          break
        }
      }
      ;# 左にたどって左ブランク位置（発話開始点）を探す
      if $estimate(S) {
        set length 0
        for {set k $j} {$k > 0} {incr k -1} {
          if {[lindex $pw $k] <= $power(uttLow)} {
            incr length
          } else {
            set length 0
          }
          if {$length >= [expr $power(silLength) + 1]} {
            ;# 現在位置を左ブランクにする
            set tm [expr $k * $power(frameLength)] ;# 時刻[sec]を計算
            set paramS($i,S) $tm
            break
          }
        }
      }

      ;# 発声音量が減衰し始める点を求める
      set length 0   ;# 発話が連続しているサンプル数
      for {set j [expr [llength $pw] - 1]} {$j > $uttS} {incr j -1} {
        if {[lindex $pw $j] >= $power(uttHigh)} {
          incr length
        } else {
          set length 0
        }
        if {$length >= [expr $power(uttLength) + 1]} {
          set uttE $j     ;# 現在位置を保存
          break
        }
      }

      ;# uttS〜uttE間の中央付近の平均パワーavePを求める
      set Nmax 30
      set N 0
      set aveP 0
      set center [expr int(($uttE + $uttS) / 2)]
      for {set j [expr $center + 1]} {$j <= $uttE && $N < [expr $Nmax / 2]} {incr j} {
        set aveP [expr $aveP + [lindex $pw $j]]
        incr N
      }
      for {set j $center} {$j >= $uttS && $N < $Nmax} {incr j -1} {
        set aveP [expr $aveP + [lindex $pw $j]]
        incr N
      }
      set aveP [expr $aveP / $N]

      ;# 右ブランク位置を探す
      if $estimate(E) {
        for {set j $center} {$j <= $uttE} {incr j} {
;#koko, パワーが平均値より大きい方に揺らいでいる分はOKとした。
;#koko, パワーが平均値より小さく揺らいだときに反応させるようにした
          if {[expr $aveP - [lindex $pw $j]] > [expr $power(uttKeep) / 2]} {
            break
          }
        }
        ;# 現在位置を右ブランクにする
        set tm [expr $j * $power(frameLength)] ;# 時刻[sec]を計算
        if {$paramS($i,S) < $tm} {
          set paramS($i,E) $tm
        } else {
          set paramS($i,E) $paramS($i,S)
        }
      }

      ;# 子音部位置を探す
      if $estimate(C) {
        for {set j $center} {$j >= $uttS} {incr j -1} {
;#koko, パワーが平均値より大きい方に揺らいでいる分はOKとした。
;#koko, パワーが平均値より小さく揺らいだときに反応させるようにした
          if {[expr $aveP - [lindex $pw $j]] > [expr $power(uttKeep) / 2]} {
            break
          }
        }
        ;# 現在位置を子音部にする
        set tm [expr $j * $power(frameLength)] ;# 時刻[sec]を計算
        if {$tm >= [expr $paramS($i,S) + $estimate(minC)]} {
          set paramS($i,C) $tm
        } else {
          set paramS($i,C) [expr $paramS($i,S) + $estimate(minC)]
        }
      }
    }

    ;# 先行発声位置を探す
    if $estimate(P) {
      set paramS($i,P) $paramS($i,S)

      ;# 有声開始位置を求める
      set seriestmp {}
      if {[catch {set seriestmp [sWork pitch -method $f0(method) \
        -framelength $f0(frameLength) -windowlength $f0(windowLength) \
        -maxpitch $f0(max) -minpitch $f0(min) \
        ] } ret]} {
        if {$ret != ""} {
          puts "error: $ret"
        }
        set seriestmp {}
      }
      for {set vot [expr int($paramS($i,P) / $f0(frameLength))]} \
          {$vot < [llength $seriestmp]} {incr vot} {
        if {[lindex [split [lindex $seriestmp $vot] " "] 0] > 0} break
      }
      set votSec [expr $vot * $f0(frameLength)]

      ;# votから右に行き、母音パワー最小値を超える所まで移動する
      if {[array names paramS "$i,C"] == ""} {
        set C $estimate(minC)
      } else {
        set C $paramS($i,C)
      }
      for {set j [expr int($votSec / $power(frameLength))]} \
          {[expr $j * $power(frameLength)] < $C} \
          {incr j} {
        if {[lindex $pw $j] >= $power(vLow)} break
      }

      ;# 現在位置を先行発声にする
      set tm [expr $j * $power(frameLength)] ;# 時刻[sec]を計算
      if {$tm <= $C} {
        set paramS($i,P) $tm
      } else {
        set paramS($i,P) $paramS($i,S)
      }
    }

    ;# オーバーラップ位置を求める
    if $estimate(O) {
      set paramS($i,O) $paramS($i,S)
      if {$estimate(ovlPattern) != "" && [lsearch $pattern $paramU($i,0)] >= 0 && 
          [array names paramS "$i,P"] != ""} {
          set newO [expr ($paramS($i,P) - $paramS($i,S)) * $estimate(ovl) + $paramS($i,S)]
          set paramS($i,O) $newO
      }
    }

    ;# paramUを設定する
    foreach kind {S E C P O} {
      if $estimate($kind) {
        set paramU($i,[kind2c $kind]) [sec2u $i $kind $paramS($i,$kind)]
      }
    }
    updateProgressWindow [expr 100 * $i / $paramUsize]
    set v(msg) "$t(doEstimateParam,startMsg) ($i / $paramUsize)"
  }
  deleteProgressWindow

  set v(paramChanged) 1
  setEPWTitle
  set v(msg) [eval format $t(doEstimateParam,doneMsg)]
}

#---------------------------------------------------
# プログレスバーを初期化して表示する
#
proc initProgressWindow {{title "now processing..."}} {
  global prgWindow v

  if {$::tcl_platform(os) == "Darwin"} {
    if [isExist $prgWindow] {  ;# macではprogressbarを二回実行すると落ちたので
      set v(progress) 0 
      wm deiconify $prgWindow
      return
    }
  } else {
    if [isExist $prgWindow] return
  }

  toplevel $prgWindow
  wm title $prgWindow $title
  if {$::tcl_platform(os) != "Darwin"} {
    wm attributes $prgWindow -toolwindow 1
    wm attributes $prgWindow -topmost 1
  }
  bind $prgWindow <Escape> "destroy $prgWindow"
  set topg [split [wm geometry .] "x+"]
  set x [expr [lindex $topg 2] + [lindex $topg 0] / 2 - 100]
  set y [expr [lindex $topg 3] + [lindex $topg 1] / 2 - 5]
  wm geometry $prgWindow "+$x+$y"

  set v(progress) 0
  ttk::progressbar $prgWindow.p -length 200 -variable v(progress) -mode determinate
  pack $prgWindow.p

  raise $prgWindow
  focus $prgWindow
  update
}

#---------------------------------------------------
# プログレスバーを更新する。進捗状況は$progress(0〜100)で指定する)
#
proc updateProgressWindow {progress {title ""}} {
  global v prgWindow

  if {$title != ""} { wm title $prgWindow $title }
  set v(progress) $progress
  raise $prgWindow
  focus $prgWindow
  update
}

#---------------------------------------------------
# プログレスバーを消去する
#
proc deleteProgressWindow {} {
  global prgWindow
  destroy $prgWindow
}

#---------------------------------------------------
# ustファイルを読み、該当する音名のみの編集環境を作る
#
proc readUstFile {} {
  global paramU paramUsize paramS v t

  ;# パラメータを変更していたなら確認する
  if $v(paramChanged) {
    set act [tk_dialog .confm $t(.confm) $t(.confm.delParam) \
      question 1 $t(.confm.yes) $t(.confm.no)]
    if {$act == 1} return
  }

  ;# 警告文表示
  set act [tk_dialog .confm $t(.confm) $t(readUstFile,startMsg) \
    question 1 $t(.confm.ok) $t(.confm.c)]
  if {$act == 1} return

  ;# ustファイル名取得
  set fn [tk_getOpenFile \
          -title $t(readParamFile,selMsg) -defaultextension "ust" \
          -filetypes { {{UTAU ust file} {.ust}} {{All Files} {*}} }]
  if {$fn == ""} return

  ;# 編集対象となる音名のリストを lyric に入れる
  set lyric {}
  if [catch {open $fn r} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  while {![eof $fp]} {
    set data [split [gets $fp] "="]
    if {[llength $data] > 1} {
      set item [lindex $data 0]       ;# 項目名
      set val  [lindex $data 1]       ;# データ内容
      if {$item == "Lyric"} {
        ;# 重複がなければリストに追加
        if {[lsearch -exact $lyric $val] < 0} {
          lappend lyric $val
        }
      }
    }
  }
  close $fp

  ;# 編集対象のパラメータ行を求める(editTarget(r)==1ならrは編集対象)
  array unset editTarget
  for {set r 1} {$r < $paramUsize} {incr r} {
    set editTarget($r) 0
  }
  foreach l $lyric {
    for {set r 1} {$r < $paramUsize} {incr r} {
      if {[array names paramU "$r,0"] != "" && $l == $paramU($r,0)} {
        set editTarget($r) 1
      } elseif {[array names paramU "$r,6"] != "" && $l == $paramU($r,6)} {
        set editTarget($r) 1
      }
    }
  }

  ;# 編集対象のみの一覧表を作る
  set paramUsizeNew 1
  set recListNew {}
  for {set r 1} {$r < $paramUsize} {incr r} {
    if $editTarget($r) {
      ;# paramUを詰める
      for {set c 0} {$c < 7} {incr c} {
        if {[array names paramU "$r,$c"] != ""} {
          set paramU($paramUsizeNew,$c) $paramU($r,$c) 
        } else {
          set paramU($paramUsizeNew,$c) 0
        }
      }
      set paramU($paramUsizeNew,R) [llength $recListNew]
      lappend recListNew $paramU($r,0)

      ;# paramSを更新
      for {set c 1} {$c < 6} {incr c} {
        set kind [c2kind $c]
        if {[array names paramS "$r,$kind"] != ""} {
          set paramS($paramUsizeNew,$kind) $paramS($r,$kind) 
        } else {
          set paramS($paramUsizeNew,$kind) ""
        }
      }

      incr paramUsizeNew
    }
  }

  ;# リストを構築できないときはエラーを返す
  if {$paramUsizeNew <= 1} {
    tk_messageBox -message [eval format $t(readUstFile,errMsg)] \
      -title $t(.confm.errTitle) -icon error
    return
  }

  ;# paramUsizeを更新
  set paramUsize $paramUsizeNew
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存

  ;# v(recList)を更新
  set v(recList) $recListNew

  resetDisplay
  ;# 一覧表のサイズを更新する
  if [winfo exists .entpwindow] {
    .entpwindow.t configure -rows $paramUsize
    setCellSelection
  }
  set v(paramChanged) 0
  setEPWTitle
  set tmp [expr $paramUsize -1]
  set v(msg) [eval format $t(readUstFile,doneMsg)]
}

#---------------------------------------------------
# 新たに原音パラメータを読み込んで読み込み済みのものにマージする
#
proc mergeParamFile {args} {
  global paramU paramS v paramUsize t

  if $v(paramChanged) {
    set act [tk_dialog .confm $t(.confm) $t(mergeParamFile,delParam) \
      question 1 $t(.confm.yes) $t(.confm.no)]
    if {$act == 1} return
  }

  if {[llength $args] == 0} {
    set fn [tk_getOpenFile -initialfile $v(paramFile) \
            -title $t(mergeParamFile,selMsg) -defaultextension "ini" \
            -filetypes { {{UTAU param file} {.ini}} {{All Files} {*}} }]
  } else {
    set fn [lindex $args 0]
  }
  if {$fn == ""} return

  ;# プログレス表示用に、一度データ数をカウントする
  set dataMax 0
  if [catch {open $fn r} fptmp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  while {![eof $fptmp]} {
    gets $fptmp
    incr dataMax
  }
  close $fptmp

  ;# マージする原音パラメータファイルを開く
  if [catch {open $fn r} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }

  ;# 結果報告する窓を表示する
  initProgressWindow "now merging..."

  set appendParam {}       ;# 上書き対象のなかったデータを入れる
  set fnameOld ""
  set cnt 0
  snack::sound sWork

  while {![eof $fp]} {
    set l [gets $fp]   ;# "fname=A,S,C,E,P,O"
    set p [split $l "=,"]
    if {[llength $p] == 7} {
      ;# ファイルIDを求める
      set fname [lindex $p 0]
      set fid [string range $fname 0 [expr [string first ".$v(ext)" $fname] - 1]]

      set alias [lindex $p 1]

      for {set r 1} {$r < $paramUsize} {incr r} {
        ;# fidおよびエイリアスが一致するデータがあれば上書きして次へ
        if {$paramU($r,0) == $fid && $paramU($r,6) == $alias} {

          ;# 空欄のパラメータを0にする
          for {set i 2} {$i < [llength $p]} {incr i} {
            if {[lindex $p $i] == ""} {
              set p [lreplace $p $i $i 0]
            }
          }
 
          ;# paramUを上書き
          set paramU($r,1) [lindex $p 2]   ;# S
          set paramU($r,4) [lindex $p 3]   ;# C
          set paramU($r,5) [lindex $p 4]   ;# E
          set paramU($r,3) [lindex $p 5]   ;# P
          set paramU($r,2) [lindex $p 6]   ;# O

          ;# paramSを更新
          if {$fname != $fnameOld} {
            sWork read "$v(saveDir)/$fname"
            set v(sndLength) [sWork length -unit SECONDS]
            set fnameOld $fname
          }
          paramU2paramS $r

          break
        }
      }
      ;# 上書きしなかったデータを記録
      if {$r >= $paramUsize} {
        lappend appendParam $l
      }
    }
    ;# 進捗を表示
    updateProgressWindow [expr 100 * $cnt / $dataMax]
    set v(msg) "$t(mergeParamFile,startMsg) ($paramUsize / $dataMax)"

    incr cnt
  }

  ;# ファイルを閉じる
  close $fp

  deleteProgressWindow

  ;# 上書きしていないデータが残った場合
  if {[llength $appendParam] > 0} {

    initProgressWindow
    set i 0
    set notExistWav {}
    foreach l $appendParam {
      set p [split $l "=,"]
      if {[llength $p] == 7} {
        ;# ファイルIDを求める
        set fname [lindex $p 0]
        set fid [string range $fname 0 [expr [string first ".$v(ext)" $fname] - 1]]

        ;# もしwavが存在しないエントリであれば登録しない
        if {[file exists "$v(saveDir)/$fname"] == 0} {
          lappend notExistWav $fid
          continue
        }

        ;# 挿入位置を求める
        for {set ins 2} {$ins < $paramUsize} {incr ins} {
          if {$paramU([expr $ins -1],0) == $fid && $paramU($ins,0) != $fid } {
            break
          }
        }
        
        ;# 挿入位置が一覧表の途中だった場合はセル内容を一行下にずらす
        if {$ins < $paramUsize} {
          for {set r $paramUsize} {$r > $ins} {incr r -1} {
            ;# paramUの内容を一つ下にコピーする
            for {set c 0} {$c < 7} {incr c} {
              if {[array names paramU [expr $r - 1],$c] != ""} {
                set paramU($r,$c) $paramU([expr $r - 1],$c)
              } else {
                array unset paramU $r,$c
              }
            }
            set paramU($r,R) $paramU([expr $r - 1],R)

            ;# paramSの内容を一つ下にコピーする
            foreach kind {S C E P O} {
              if {[array names paramS [expr $r - 1],$kind] != ""} {
                set paramS($r,$kind) $paramS([expr $r - 1],$kind)
              } else {
                array unset paramS $r,$kind
              }
            }
          }
        }

        ;# paramUを求める
        set paramU($ins,0) $fid            ;# fid
        set paramU($ins,6) [lindex $p 1]   ;# A
        set paramU($ins,1) [lindex $p 2]   ;# S
        set paramU($ins,4) [lindex $p 3]   ;# C
        set paramU($ins,5) [lindex $p 4]   ;# E
        set paramU($ins,3) [lindex $p 5]   ;# P
        set paramU($ins,2) [lindex $p 6]   ;# O
        set paramU($ins,R) [llength $v(recList)]

        ;# recListに追加
        lappend v(recList) $fid

        ;# paramSを求める
        sWork read "$v(saveDir)/$fname"
        set v(sndLength) [sWork length -unit SECONDS]
        paramU2paramS $ins

        updateProgressWindow [expr 100 * $i / [llength $appendParam]]
        incr i
        incr paramUsize
      }
    }
  }
  resetDisplay
  ;# 一覧表のサイズを更新する
  if [winfo exists .entpwindow] {
    .entpwindow.t configure -rows $paramUsize
    setCellSelection
  }
  set v(paramChanged) 1
  setEPWTitle
  deleteProgressWindow
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存
  set v(msg) [eval format $t(mergeParamFile,doneMsg)]
}

#---------------------------------------------------
# 原音パラメータを読み込む
#
proc readParamFile {args} {
  global paramU paramS v paramUsize t

  if $v(paramChanged) {
    set act [tk_dialog .confm $t(.confm) $t(.confm.delParam) \
      question 1 $t(.confm.yes) $t(.confm.no)]
    if {$act == 1} return
  }

  if {[llength $args] == 0} {
    set fn [tk_getOpenFile -initialfile $v(paramFile) \
            -title $t(readParamFile,selMsg) -defaultextension "ini" \
            -filetypes { {{UTAU param file} {.ini}} {{All Files} {*}} }]
  } else {
    set fn [lindex $args 0]
  }
  if {$fn == ""} return

  #if {![file exists "$v(saveDir)/oto.ini"]} {
  #  tk_messageBox -message "$v(saveDir)/oto.iniを読み込めませんでした" \
  #    -icon warning -title ファイルI/Oエラー
  #  return
  #}

  ;# プログレス表示用に、一度データ数をカウントする
  set dataMax 0

  if [catch {open $fn r} fptmp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  while {![eof $fptmp]} {
    gets $fptmp
    incr dataMax
  }
  close $fptmp

  ;# 原音パラメータファイルを開く
  if [catch {open $fn r} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  set v(paramFile) $fn

  ;# キャッシュを読むかチェック
  ;# oto.iniよりキャッシュファイルの日付が新しいなら利用する(useCache=1にする)
  set useCache 0
  set cacheFn [format "%s.oremo-Scache" [file rootname $v(paramFile)]]
  if [file readable $cacheFn] {
    if {[file mtime $cacheFn] >= [file mtime $v(paramFile)]} {
      set useCache 1
    }
  }

  ;# 結果報告する窓を表示する
  initProgressWindow "now loading..."

  set reclistO {}  ;# oto.iniから生成したreclistを入れる
  initParamS
  initParamU 1
  set fnameOld ""
  set notExistWav {} ;# wavファイルの存在しないfidを入れる
  snack::sound sWork

  while {![eof $fp]} {
    set l [gets $fp]   ;# "fname=A,S,C,E,P,O"
    set p [split $l "=,"]
    if {[llength $p] == 7} {
      ;# ファイルIDを求める
      set fname [lindex $p 0]
      set fid [string range $fname 0 [expr [string first ".$v(ext)" $fname] - 1]]

      ;# もしwavが存在しないエントリであれば登録しない
      if {[file exists "$v(saveDir)/$fname"] == 0} {
        lappend notExistWav $fid
        continue
      }

      ;# 空欄のパラメータは0にする
      for {set i 2} {$i < [llength $p]} {incr i} {
        if {[lindex $p $i] == ""} {
          set p [lreplace $p $i $i 0]
        }
      }
 
      ;# paramUを求める
      set paramU($paramUsize,0) $fid            ;# fid
      set paramU($paramUsize,6) [lindex $p 1]   ;# A
      set paramU($paramUsize,1) [lindex $p 2]   ;# S
      set paramU($paramUsize,4) [lindex $p 3]   ;# C
      set paramU($paramUsize,5) [lindex $p 4]   ;# E
      set paramU($paramUsize,3) [lindex $p 5]   ;# P
      set paramU($paramUsize,2) [lindex $p 6]   ;# O
      ;# 内部で参照するデータを設定
      set chofuku [lsearch -exact $reclistO $fid]
      if {$chofuku >= 0} {
        set paramU($paramUsize,R) $chofuku
      } else {
        set paramU($paramUsize,R) [llength $reclistO]
      }

      ;# reclistOに追加(重複する場合は追加しない)
      if {$chofuku < 0} {
        lappend reclistO $fid
      }

      ;# paramSを求める
      if {$useCache == 0 && [file exists "$v(saveDir)/$fname"]} {
        if {$fname != $fnameOld} {
          sWork read "$v(saveDir)/$fname"
          set v(sndLength) [sWork length -unit SECONDS]
        }
        paramU2paramS $paramUsize
      }

      incr paramUsize
      set fnameOld $fname
    }
    ;# 進捗を表示
    updateProgressWindow [expr 100 * $paramUsize / $dataMax]
    set v(msg) "$t(readParamFile,startMsg) ($paramUsize / $dataMax)"
  }

  if $useCache readCacheFile

  ;# ファイルを閉じる
  close $fp

  deleteProgressWindow

  ;# reclistOがv(recList)に一致するかチェック
  set rlV $v(recList)
  set i 0
  while {$i < [llength $rlV]} {
    if {[lsearch -exact $reclistO [lindex $rlV $i]] >= 0} {
      ;# 同じエントリがあった場合エントリを削除する
      set rlV [lreplace $rlV $i $i]
    } else {
      ;# reclistOにあるエントリがv(recList)にない場合
      incr i
    }
  }
  ;# 存在しないwav用のパラメータがある場合
  if {[llength $notExistWav] > 0} {
    set example [lindex $notExistWav 0]
    set mes [eval format $t(readParamFile,errMsg)]
    set mes "$mes\n$t(readParamFile,example)$example.$v(ext)"
    tk_messageBox -message $mes -icon warning -title $t(.confm.warnTitle)
  }
  ;# v(recList)にエントリが残った場合
  if {[llength $rlV] > 0} {
    set example [lindex $rlV 0]
    set mes [eval format $t(readParamFile,errMsg2)]
    set mes "$mes\n$t(readParamFile,example)$example.$v(ext)"
    if {[llength $rlV] >= 2} {
      set example [lindex $rlV 1]
      set mes "$mes\n$t(readParamFile,example)$example.$v(ext) ..."
    }
    tk_messageBox -message $mes -icon warning -title $t(.confm.warnTitle)

    initProgressWindow
    set i 0
    foreach fid $rlV {
      ;# paramUを求める
      set paramU($paramUsize,0) $fid            ;# fid
      set paramU($paramUsize,6) ""  ;# A
      set paramU($paramUsize,1) 0   ;# S
      set paramU($paramUsize,4) 0   ;# C
      set paramU($paramUsize,5) 0   ;# E
      set paramU($paramUsize,3) 0   ;# P
      set paramU($paramUsize,2) 0   ;# O
      ;# 内部で参照するデータを設定
      set paramU($paramUsize,R) [llength $reclistO]

      ;# reclistOに追加
      lappend reclistO $fid

      ;# paramSを求める
      if {[file exists "$v(saveDir)/$fid.$v(ext)"]} {
        sWork read "$v(saveDir)/$fid.$v(ext)"
        set v(sndLength) [sWork length -unit SECONDS]
        paramU2paramS $paramUsize
      }

      updateProgressWindow [expr 100 * $i / [llength $rlV]]
      incr i
      incr paramUsize
    }
  }
  ;# ファイルから生成したリストをv(recList)に登録する
  set v(recList) $reclistO

  resetDisplay
  ;# 一覧表のサイズを更新する
  if [winfo exists .entpwindow] {
    .entpwindow.t configure -rows $paramUsize
    setCellSelection
  }
  set v(paramChanged) 0
  setEPWTitle
  deleteProgressWindow
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存
  set v(msg) [eval format $t(readParamFile,doneMsg)]
}

#---------------------------------------------------
# 自動バックアップを開始する
#
proc autoBackup {} {
  global v t topdir

  ;# もしバックアップファイルが存在していたら警告を出す
  if {[file exists $v(backupParamFile)] != 0} {
    set act [tk_dialog .confm $t(.confm) $t(autoBackup,q) \
      question 0 $t(autoBackup,a1) $t(autoBackup,a2)]
    if {$act == 0} {
      exec $::env(COMSPEC) /c start "$topdir"    ;# backup.iniのあるフォルダを開く
      exit
    }
    file delete $v(backupParamFile)
  }
  ;# 定期バックアップ開始
  if {$v(autoBackup) > 0} {
    after [expr $v(autoBackup) * 60000] doAutoBackup
  }
}

#---------------------------------------------------
# 自動バックアップをとる
#
proc doAutoBackup {} {
  global v t

  if {$v(autoBackup) <= 0} return
  set paramFileTmp $v(paramFile)
  saveParamFile $v(backupParamFile)  1
  set v(paramFile) $paramFileTmp ;# ファイル名を元に戻す
  after [expr $v(autoBackup) * 60000] doAutoBackup
}

#---------------------------------------------------
# 原音パラメータを保存する
# return: 1=保存した。0=保存しなかった。
# 引数1 fn: 保存ファイル名。指定なしの場合ダイアログ窓を開く
# 引数2 autoBackup: 0=通常のoto.ini保存。
#         1=自動バックアップのための保存。キャッシュファイルを保存しない。
#           保存した際にメッセージを表示しない。
#
proc saveParamFile {{fn ""} {autoBackup 0}} {
  global paramU paramUsize v t
 
  ;#if {$v(paramChanged) == 0} {
  ;#  set v(msg) "パラメータが変更されていないので保存しませんでした"
  ;#  tk_dialog .confm "Warning" \
  ;#    "パラメータが変更されていないので保存しませんでした" \
  ;#    warning 0 OK
  ;#  return 0
  ;#}
  if {$fn == ""} {
    set fn [tk_getSaveFile -initialfile $v(paramFile) \
              -title $t(saveParamFile,selFile) -defaultextension "ini" ]
    if {$fn == ""} {return 0}
  }

  if {$autoBackup == 0} {
    set v(msg) $t(saveParamFile,startMsg)
  }

  if {[file exists $v(saveDir)] == 0} {
    file mkdir $v(saveDir)
  }
  ;# 保存ファイルを開く
  if [catch {open $fn w} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  if {$::tcl_platform(os) != "Darwin"} {
    set v(paramFile) $fn  ;# macだとパス解析に失敗して次回のmy_getSaveFileで不正確なフォルダが初期指定されたので
  }

  for {set i 1} {$i < $paramUsize} {incr i} {
    if {[array names paramU "$i,0"] != ""} {
      set name $paramU($i,0).$v(ext)
      set S 0; set O 0; set P 0; set C 0; set E 0; set A "";
      if {[array names paramU "$i,1"] != ""} { set S $paramU($i,1) }
      if {[array names paramU "$i,2"] != ""} { set O $paramU($i,2) }
      if {[array names paramU "$i,3"] != ""} { set P $paramU($i,3) }
      if {[array names paramU "$i,4"] != ""} { set C $paramU($i,4) }
      if {[array names paramU "$i,5"] != ""} { set E $paramU($i,5) }
      if {[array names paramU "$i,6"] != ""} { set A $paramU($i,6) }
      puts $fp $name=$A,$S,$C,$E,$P,$O    ;# ファイルへ書き出し
    }
  }
  close $fp        ;# ファイルを閉じる

  if {$::tcl_platform(os) == "Darwin"} {
    global nkf 
    exec -- $nkf -s --in-place $fn      ;# 漢字コードをsjisに変換
  }
  if {$autoBackup == 0} {
    if {$v(appname) != "OREMO"} {
      saveCacheFile    ;# キャッシュ作成。これはoto.ini保存より先にやってはいけない。
      set v(paramChanged) 0
      setEPWTitle
    }
    set v(msg) [eval format $t(saveParamFile,doneMsg)]
  }
  return 1
}

#---------------------------------------------------
# 原音パラメータの内部形式 paramS を保存する
# return: 1=保存した。0=保存しなかった。
#
proc saveCacheFile {} {
  global paramS paramUsize v t

  if {$v(appname) != "setParam" || $v(paramFile) == ""} {return 0}
  set fn [format "%s.%s-Scache" [file rootname $v(paramFile)] $v(appname)]
 
  ;# 保存ファイルを開く
  if [catch {open $fn w} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  fconfigure $fp -translation binary  ;# バイナリモードにする

  for {set r 1} {$r < $paramUsize} {incr r} {
    for {set c 1} {$c < 6} {incr c} {
      set val 0
      set kind [c2kind $c]
      if {[array names paramS "$r,$kind"] != ""} { set val $paramS($r,$kind) }
      puts -nonewline $fp [binary format d1 $val]   ;# ファイルへ書き出し
    }
  }
  close $fp        ;# ファイルを閉じる
  return 1
}

#---------------------------------------------------
# キャッシュファイルを読み込む
#
proc readCacheFile {} {
  global paramS v t

  set fn [format "%s.%s-Scache" [file rootname $v(paramFile)] $v(appname)]

  if {$fn == "" || ! [file readable $fn]} {return 0}

  ;# 保存ファイルを開く
  if [catch {open $fn r} fp] { 
    tk_messageBox -message "error: can not open $fn" \
      -title $t(.confm.fioErr) -icon warning
    return
  }
  fconfigure $fp -translation binary  ;# バイナリモードにする

  set r 1
  while {![eof $fp]} {
    for {set c 1} {$c < 6} {incr c} {
      set val 0
      set kind [c2kind $c]
      set buf [binary scan [read $fp 8] d1 paramS($r,$kind)]
    }
    incr r
  }
  close $fp        ;# ファイルを閉じる
  return 1
}


#---------------------------------------------------
# 詳細設定
#
proc settings {} {
  global swindow v power f0 v_bk power_bk f0_bk snd t
    # ↑*_bkは大域変数にしないとキャンセル時にバックアップ復帰できなかった

  ;# 二重起動を防止
  if [isExist $swindow] return
  toplevel $swindow
  wm title $swindow $t(settings,title)
  wm resizable $swindow 0 0
  bind $swindow <Escape> "destroy $swindow"

  array set v_bk     [array get v]     ;# パラメータバックアップ
  array set power_bk [array get power] ;# パラメータバックアップ
  array set f0_bk    [array get f0]    ;# パラメータバックアップ

  ;# 1カラム目のフレーム
  set frame1 [frame $swindow.l]
  pack $frame1 -side left -anchor n -fill y -padx 2 -pady 2

  ;#---------------------------
  ;# 波形
  set lf1 [labelframe $frame1.lf1 -text $t(settings,wave) \
    -relief groove -padx 5 -pady 5]
  pack $lf1 -anchor w -fill x

  ;# 波形色の設定
  set cw [frame $lf1.f4w]
  setColor $cw "wavColor" $t(settings,waveColor)
  pack $cw -anchor nw

  ;# 波形縦軸の最大値
  pack [frame $lf1.fs] -anchor w
  label $lf1.fs.l -text $t(settings,waveScale) -wi 35 -anchor w
  entry $lf1.fs.e -textvar v(waveScale) -wi 6 -validate key -validatecommand {
    if {![string is integer %P]} {return 0}
    if {%P < 0 || %P > 32768} {return 0}
    return 1
  }
  pack $lf1.fs.l $lf1.fs.e  -side left

  ;# サンプリング周波数の設定
  pack [frame $lf1.f20] -anchor w
  label $lf1.f20.l -text $t(settings,sampleRate) -wi 35 -anchor w
  entry $lf1.f20.e -textvar v(sampleRate) -wi 6 -validate key -validatecommand {
    if {![string is integer %P]} {return 0}
    if {%P < 0} {return 0}
    return 1
  }
  pack $lf1.f20.l $lf1.f20.e  -side left

  ;#---------------------------
  ;# スペクトルパラメータ
  set lf2 [labelframe $frame1.lf2 -text $t(settings,spec) \
    -relief groove -padx 5 -pady 5]
  pack $lf2 -anchor w -fill x

  ;# スペクトルの配色
  pack [frame $lf2.f45] -anchor w
  label $lf2.f45.l -text $t(settings,specColor) -width 20 -anchor w
  tk_optionMenu $lf2.f45.cm v(cmap) grey color1 color2
  pack $lf2.f45.l $lf2.f45.cm -side left

  ;# スペクトル周波数の最高値
  pack [frame $lf2.f20] -anchor w
  label $lf2.f20.l -text $t(settings,maxFreq) -width 20 -anchor w
  entry $lf2.f20.e -textvar v(topfr) -wi 6
  scale $lf2.f20.s -variable v(topfr) -orient horiz \
    -from 0 -to [expr $v(sampleRate)/2] -showvalue 0
  pack $lf2.f20.l $lf2.f20.e $lf2.f20.s -side left

  ;# 明るさ
  pack [frame $lf2.f30] -anchor w
  label $lf2.f30.l -text $t(settings,brightness) -width 20 -anchor w
  entry $lf2.f30.e -textvar v(brightness) -wi 6
  scale $lf2.f30.s -variable v(brightness) -orient horiz \
    -from -100 -to 100 -res 0.1 -showvalue 0
  pack $lf2.f30.l $lf2.f30.e $lf2.f30.s -side left

  ;# コントラスト
  pack [frame $lf2.f31] -anchor w
  label $lf2.f31.l -text $t(settings,contrast) -width 20 -anchor w
  entry $lf2.f31.e -textvar v(contrast) -wi 6
  scale $lf2.f31.s -variable v(contrast) -orient horiz \
    -from -100 -to 100 -res 0.1 -showvalue 0
  pack $lf2.f31.l $lf2.f31.e $lf2.f31.s -side left

  ;# FFT長(必ず2のべき乗にすること)
  pack [frame $lf2.f32] -anchor w
  label $lf2.f32.l -text $t(settings,fftLength) -width 20 -anchor w
  tk_optionMenu $lf2.f32.om v(fftlen) 8 16 32 64 128 256 512 1024 2048 4096
  pack $lf2.f32.l $lf2.f32.om -side left

  ;# 窓長(必ずFFT長以下にすること)
  pack [frame $lf2.f33] -anchor w
  label $lf2.f33.l -text $t(settings,fftWinLength) -width 20 -anchor w
  entry $lf2.f33.e -textvar v(winlen) -wi 6
  scale $lf2.f33.s -variable v(winlen) -orient horiz \
    -from 8 -to 4096 -showvalue 0
  pack $lf2.f33.l $lf2.f33.e $lf2.f33.s -side left

  ;# プリエンファシス
  pack [frame $lf2.f34] -anchor w
  label $lf2.f34.l -text $t(settings,fftPreemph) -width 20 -anchor w
  entry $lf2.f34.e -textvar v(preemph) -wi 6
  pack $lf2.f34.l $lf2.f34.e -side left

  ;# 窓の選択
  pack [frame $lf2.f35] -anchor w
  label $lf2.f35.lwn -text $t(settings,fftWinKind) -width 20 -anchor w
  tk_optionMenu $lf2.f35.mwn v(window) \
    Hamming Hanning Bartlett Blackman Rectangle
  pack $lf2.f35.lwn $lf2.f35.mwn -side left

  ;#---------------------------
  ;# パワーの設定
  set lf3 [labelframe $frame1.lf3 -text $t(settings,pow) \
    -relief groove -padx 5 -pady 5]
  pack $lf3 -anchor w -fill x

  ;# パワー色の設定
  set cp [frame $lf3.f4p]
  setColor $cp "powcolor" $t(settings,powColor)
  pack $cp -anchor nw

  ;# パワー抽出刻みの設定
  packEntryPower $lf3.ffl $t(settings,powLength) frameLength

  ;# プリエンファシスの設定
  packEntryPower $lf3.fem $t(settings,powPreemph) preemphasis

  ;# 窓長の設定
  packEntryPower $lf3.fwl $t(settings,winLength) windowLength

  ;# 窓の選択
  pack [frame $lf3.fwn] -anchor w
  label $lf3.fwn.lwn -text $t(settings,powWinKind) -width 20 -anchor w
  tk_optionMenu $lf3.fwn.mwn power(window) \
    Hamming Hanning Bartlett Blackman Rectangle
  pack $lf3.fwn.lwn $lf3.fwn.mwn -side left

  ;#---------------------------
  ;#---------------------------
  ;# 2カラム目のフレーム
  set frame2 [frame $swindow.r]
  pack $frame2 -side left -anchor n -fill both -expand true -padx 2 -pady 2

  ;#---------------------------
  ;# F0の設定
  set lf4 [labelframe $frame2.lf4 -text $t(settings,f0) \
    -relief groove -padx 5 -pady 5]
  pack $lf4 -anchor w -fill x

  ;# F0色の設定
  set cf [frame $lf4.f4f]
  setColor $cf "f0color" $t(settings,f0Color)
  pack $cf -anchor nw

  ;# 抽出アルゴリズムの選択
  pack [frame $lf4.p1] -anchor w
  label $lf4.p1.l -text $t(settings,f0Argo) -width 20 -anchor w
  tk_optionMenu $lf4.p1.mt f0(method) ESPS AMDF
  pack $lf4.p1.l $lf4.p1.mt -side left

  ;# entry型の設定いろいろ
  packEntryF0 $lf4.p2 $t(settings,f0Length)    frameLength
  packEntryF0 $lf4.p3 $t(settings,f0WinLength) windowLength
  packEntryF0 $lf4.p4 $t(settings,f0Max)       max
  packEntryF0 $lf4.p5 $t(settings,f0Min)       min

  ;# 表示単位の選択
  pack [frame $lf4.p6] -anchor w
  label $lf4.p6.l -text $t(settings,f0Unit) -width 20 -anchor w
  tk_optionMenu $lf4.p6.mt f0(unit) Hz semitone
  pack $lf4.p6.l $lf4.p6.mt -side left

  ;# グラフ範囲の設定
  checkbutton $lf4.p7cb -text $t(settings,f0FixRange) \
    -variable f0(fixShowRange) -onvalue 1 -offvalue 0 -anchor w
  pack [labelframe $lf4.p7 -labelwidget $lf4.p7cb \
    -relief ridge -padx 5 -pady 5] -anchor w -fill x
  packToneList $lf4.p7.tl1 $t(settings,f0FixRange,h) \
    showMaxTone showMaxOctave showMaxTmp 10 checkVol
  packToneList $lf4.p7.tl2 $t(settings,f0FixRange,l) \
    showMinTone showMinOctave showMinTmp 10 checkVol

  ;# 各音の線を表示
  checkbutton  $lf4.p8cb -text $t(settings,grid) \
    -variable f0(showToneLine) -onvalue 1 -offvalue 0 -anchor w
  pack [labelframe $lf4.p8 -labelwidget $lf4.p8cb \
    -relief ridge -padx 5 -pady 5] -anchor w -fill x
  setColor $lf4.p8 "toneLineColor" $t(settings,gridColor)

  ;# ターゲット音の線を表示
  checkbutton $lf4.p9cb -text $t(settings,target) \
    -variable f0(showTgtLine) -onvalue 1 -offvalue 0 -anchor w
  pack [labelframe $lf4.p9 -labelwidget $lf4.p9cb \
    -relief ridge -padx 5 -pady 5] -anchor w -fill x
  packToneList $lf4.p9.tl $t(settings,targetTone) \
    tgtTone tgtOctave tgtFreqTmp 10 checkVol
  setColor $lf4.p9 "tgtf0color" $t(settings,targetColor)
  label  $lf4.p9.al -text $t(settings,autoSetting) -anchor nw
  button $lf4.p9.ab -text $t(.confm.run) -command autoF0Settings
  pack $lf4.p9.al $lf4.p9.ab -side left

  ;# 各音名に対応する周波数を自動計算して表示する(非常に汚いやり方)
  ;# 音名orオクターブに変化があれば周波数計算を行う
  ;# 周波数をf0(tgtFreq)などでなくf0(tgtFreqTmp)などに入れるのは、
  ;#「OK」or「適用」ボタンを押すまで値変更を反映させないため。
  set f0(showMaxTmp) [tone2freq "$f0(showMaxTone)$f0(showMaxOctave)"]
  set f0(showMinTmp) [tone2freq "$f0(showMinTone)$f0(showMinOctave)"]
  set f0(tgtFreqTmp) [tone2freq "$f0(tgtTone)$f0(tgtOctave)"]
  trace variable f0 w calcFreq
  proc calcFreq {var elm mode} {
    global f0 t
    switch $elm {
      "showMaxTone" -
      "showMaxOctave" {
        set f0(showMaxTmp) [tone2freq "$f0(showMaxTone)$f0(showMaxOctave)"]
      }
      "showMinTone" -
      "showMinOctave" {
        set f0(showMinTmp) [tone2freq "$f0(showMinTone)$f0(showMinOctave)"]
      }
      "tgtTone" -
      "tgtOctave" {
        set f0(tgtFreqTmp) [tone2freq "$f0(tgtTone)$f0(tgtOctave)"]
      }
    }
  }
  bind $swindow <Destroy> { trace vdelete f0 w calcFreq }

  ;#---------------------------
  ;# OK, Apply, キャンセルボタン
  pack [frame $frame2.f] -anchor e -side bottom -padx 2 -pady 2
  button $frame2.f.exit -text $t(.confm.c) -command {
    array set v     [array get v_bk]     ;# パラメータを以前の状態に戻す
    array set power [array get power_bk] ;# パラメータを以前の状態に戻す
    array set f0    [array get f0_bk]    ;# パラメータを以前の状態に戻す
    Redraw all
    destroy $swindow
  }
  button $frame2.f.app -text $t(.confm.apply) -command {
    ;# サンプリング周波数の変更
    if {$v(sampleRate) != $v_bk(sampleRate)} {
      snd configure -rate $v(sampleRate)
    }
    ;# ターゲット音の周波数を求める
    set f0(tgtFreq) [tone2freq "$f0(tgtTone)$f0(tgtOctave)"]
    ;# F0表示範囲周波数を求める
    if $f0(fixShowRange) {
      set f0(showMin) [tone2freq "$f0(showMinTone)$f0(showMinOctave)"]
      set f0(showMax) [tone2freq "$f0(showMaxTone)$f0(showMaxOctave)"]
    }
    Redraw all
    ;# パラメータバックアップの更新
    array set v_bk     [array get v]     ;# パラメータバックアップ
    array set power_bk [array get power] ;# パラメータバックアップ
    array set f0_bk    [array get f0]    ;# パラメータバックアップ
  }
  button $frame2.f.ok -text $t(.confm.ok) -wi 6 -command {
    ;# サンプリング周波数の変更
    if {$v(sampleRate) != $v_bk(sampleRate)} {
      snd configure -rate $v(sampleRate)
    }
    ;# ターゲット音の周波数を求める
    set f0(tgtFreq) [tone2freq "$f0(tgtTone)$f0(tgtOctave)"]
    ;# F0表示範囲周波数を求める
    if $f0(fixShowRange) {
      set f0(showMin) [tone2freq "$f0(showMinTone)$f0(showMinOctave)"]
      set f0(showMax) [tone2freq "$f0(showMaxTone)$f0(showMaxOctave)"]
    }
    Redraw all
    destroy $swindow
  }
  pack $frame2.f.exit $frame2.f.app $frame2.f.ok -side right
}

#---------------------------------------------------
# F0ターゲットに合わせて他の設定値を自動設定する
#
proc autoF0Settings {} {
  global v f0

  set tgtFreq [tone2freq "$f0(tgtTone)$f0(tgtOctave)"]

  set f0(max) [expr int($tgtFreq + 200)]
  if {$tgtFreq >= 260} {
    set f0(min) [expr int($tgtFreq - 200)]
  } else {
    set f0(min) 60
  }

  set f0(fixShowRange) 1
  set ret [calcTone $f0(tgtTone) $f0(tgtOctave) 2]
  set f0(showMaxTone)   [lindex $ret 0]
  set f0(showMaxOctave) [lindex $ret 1]
  if {$f0(showMaxOctave) > $v(sinScaleMax)} {
    set f0(showMaxOctave) $v(sinScaleMax)
    set f0(showMaxTone)   [lindex $v(toneList) [expr [llength $v(toneList)] -1]]
  }
  set ret [calcTone $f0(tgtTone) $f0(tgtOctave) -2]
  set f0(showMinTone)   [lindex $ret 0]
  set f0(showMinOctave) [lindex $ret 1]
  if {$f0(showMinOctave) < $v(sinScaleMin)} {
    set f0(showMinOctave) $v(sinScaleMin)
    set f0(showMinTone)   [lindex $v(toneList) 0]
  }
  set f0(showMin) [tone2freq "$f0(showMinTone)$f0(showMinOctave)"]
  set f0(showMax) [tone2freq "$f0(showMaxTone)$f0(showMaxOctave)"]
}

#---------------------------------------------------
# tone-octaveをadd度？上げたときのトーンとオクターブのリストを返す
#
proc calcTone {tone octave add} {
  global v
  for {set i 0} {$i < [llength $v(toneList)]} {incr i} {
    if {$tone == [lindex $v(toneList) $i]} break
  }
  set seq [expr $i + $add]
  while {$seq >= [llength $v(toneList)]} {
    incr octave
    incr seq -12
  }
  while {$seq < 0} {
    incr octave -1
    incr seq +12
  }
  set ret {}
  lappend ret [lindex $v(toneList) $seq]
  lappend ret $octave
  return $ret
}

#---------------------------------------------------
# キャンバス再描画
#
proc Redraw {opt} {
  global v c cYaxis snd power f0 paramS rec t

  # 描画中は他の操作ができないようにする
  # grab set $c
  # ↑これがあると窓の隅をドラッグしてサイズ変更できなくなるのでボツ

  ;# キャンバス上のものを削除して高さを再調整する
  set v(cHeight) [expr $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh)]
  if {$v(cHeight) < [winfo height $rec]} {set v(cHeight) [winfo height $rec]}
  set cWholeWidth [expr int($v(sndLength) * $v(wavepps))]
  if {$opt != "param"} {
    $c delete obj
    $c delete axis
  }
  $c configure -height $v(cHeight) -width $v(cWidth)
  $c configure -scrollregion [list 0 0 $cWholeWidth $v(cHeight)]
  if {$opt != "param"} {
    $c create line 0 0 $cWholeWidth 0 -tags axis -fill $v(fg)
  }

  if {$opt != "param"} {
    $cYaxis delete axis
  }
  $cYaxis configure -height $v(cHeight)
  $cYaxis create line 0 2 $v(yaxisw) 2 -tags axis -fill $v(fg)
  $cYaxis create line $v(yaxisw) 0 $v(yaxisw) $v(cHeight) -tags axis -fill $v(fg)

  ;# 波形表示
  if {$v(showWave) && $opt != "opt"} {
    $c create waveform 0 0 -sound snd -height $v(waveh) -width $cWholeWidth \
      -tags [list obj wave] -debug $::debug -fill $v(wavColor) \
      -pixelspersecond $v(wavepps) -limit $v(waveScale)
    $c lower wave
    if {$v(waveScale) > 0} {
      $cYaxis create text $v(yaxisw) 4 -text $v(waveScale) \
        -font $v(sfont) -anchor ne -tags axis -fill #0000b0
      $cYaxis create text $v(yaxisw) [expr $v(waveh) * 0.5] -text [snd max] \
        -font $v(sfont) -anchor se -tags axis -fill $v(fg)
      $cYaxis create text $v(yaxisw) [expr $v(waveh) * 0.5 + 14] -text [snd min] \
        -font $v(sfont) -anchor se -tags axis -fill $v(fg)
    } else {
      $cYaxis create text $v(yaxisw) 4 -text [snd max] \
        -font $v(sfont) -anchor ne -tags axis -fill $v(fg)
      $cYaxis create text $v(yaxisw) $v(waveh) -text [snd min] \
        -font $v(sfont) -anchor se -tags axis -fill $v(fg)
    }

    set ylow $v(waveh)
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis -fill $v(fg)
    set yAxisLow [expr $v(waveh) + 2]
    $cYaxis create line 0 $yAxisLow $v(yaxisw) $yAxisLow -tags axis -fill $v(fg)
#    $c create line $v(yaxisw) 0 $v(yaxisw) $v(waveh) -tags axis -fill $v(fg)
  }

  ;# スペクトル表示
  if {$v(showSpec) && $opt != "opt"} {
    if {$v(winlen) > $v(fftlen)} {
      set v(winlen) $v(fftlen)
    }
    $c create spectrogram 0 $v(waveh) -sound snd -height $v(spech) \
      -width $cWholeWidth -tags [list obj spec] -debug $::debug \
      -fftlength $v(fftlen) -winlength $v(winlen) -windowtype $v(window) \
      -topfr $v(topfr) -contrast $v(contrast) -brightness $v(brightness) \
      -preemph $v(preemph) -colormap $v($v(cmap)) -topfrequency $v(topfr) \
      -pixelspersecond $v(wavepps)
    $c lower spec
    snack::frequencyAxis $cYaxis 0 $v(waveh) $v(yaxisw) $v(spech) \
          -topfr $v(topfr) -tags axis -font $v(sfont)
    set ylow [expr $v(spech) + $v(waveh)]
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis
    set yAxisLow [expr $ylow + 2]
    $cYaxis create line 0 $yAxisLow $v(yaxisw) $yAxisLow -tags axis -fill $v(fg)
#    $c create line $v(yaxisw) $v(waveh) $v(yaxisw) $ylow -tags axis -fill $v(fg)
  }

  ;# パワー表示
  if {$v(showpow) && $opt != "opt"} {
    ;# パワーを抽出
    set ytop [expr $v(waveh) + $v(spech)]
    set ylow [expr $ytop     + $v(powh)]
    if {$power(fid) != $v(recLab)} {
      set power(power) [snd power -framelength $power(frameLength) \
      -windowtype $power(window) -preemphasisfactor $power(preemphasis) \
      -windowlength [expr int($power(windowLength) * $v(sampleRate))] \
      -start 0 -end -1]
      if {[llength $power(power)] > 0} {
        # パワーの最大値・最小値を求める
        set power(powerMax) [lindex $power(power) 0]
        set power(powerMin) [lindex $power(power) 0]
        for {set i 1} {$i < [llength $power(power)]} {incr i} {
          if {$power(powerMax) < [lindex $power(power) $i]} {
            set power(powerMax) [lindex $power(power) $i]
          }
          if {$power(powerMin) > [lindex $power(power) $i]} {
            set power(powerMin) [lindex $power(power) $i]
          }
        }
      }
    }

    if {[llength $power(power)] > 0} {
      # ppd= 1dBあたりのピクセル数。4は上下各2ピクセルのマージン
      if {[expr $power(powerMax) - $power(powerMin)] > 0} {
        set ppd [expr double($v(powh)) / ($power(powerMax) - $power(powerMin))]
      } else {
        set ppd 0
      }

      set coord {} ;# パワー曲線を引く座標(x,y)列
      for {set i 0} {$i < [llength $power(power)]} {incr i} {
        lappend coord \
          [expr $i * $power(frameLength) * $v(wavepps)] \
          [expr $ylow - ([lindex $power(power) $i] - $power(powerMin)) * $ppd]
      }
      set pwtags {obj pow}
      eval {$c create line} $coord -tags {$pwtags} -fill $v(powcolor)
      powerAxis $cYaxis 0 $ytop $v(yaxisw) $v(powh) \
        -pwMax $power(powerMax) -tags axis -fill $v(fg) \
        -font $v(sfont) -pwMin $power(powerMin)
    }
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis
#    $c create line $v(yaxisw) $ytop $v(yaxisw) $ylow -tags axis -fill $v(fg)
    set yAxisLow [expr $ylow + 2]
    $cYaxis create line 0 $yAxisLow $v(yaxisw) $yAxisLow -tags axis -fill $v(fg)

    ;# パワーを抽出したならそのFIDを記録する
    if {$power(fid) != $v(recLab)} {
      set power(fid) $v(recLab)
    }
  }

  ;# F0表示
  if {$v(showf0) && $opt != "param"} {
    set ytop [expr $v(waveh) + $v(spech) + $v(powh)]
    set ylow [expr $ytop + $v(f0h)]
    ;# F0抽出用に波形を正規化する(小声やInt16以外の形式に対応させるため)
    snack::sound sndF0
    sndF0 copy snd
    if {[sndF0 cget -channels] > 1} {
      set sndF0 [sndF0 convert -channels Mono]
    }
    set amp [expr [sndF0 max] - [sndF0 min]]
    if {$amp > 0} {
      sndF0 filter [snack::filter map [expr 65535.0 / $amp]]
    }
    ;# F0を抽出
    if {$f0(fid) != $v(recLab)} {
      set seriestmp {}
      if {[catch {set seriestmp [sndF0 pitch -method $f0(method) \
        -framelength $f0(frameLength) -windowlength $f0(windowLength) \
        -maxpitch $f0(max) -minpitch $f0(min) \
        -progress waitWindow] } ret]} {
        if {$ret != ""} {
          puts "error: $ret"
        }
        set seriestmp {}
      }
      set f0(f0) {}
      foreach s $seriestmp {
        set val [lindex [split $s " "] 0]
        if {$f0(unit) == "semitone" && $val > 0} {
          set val [hz2semitone $val]
        }
        lappend f0(f0) $val
      }

      if {[llength $f0(f0)] > 0} {
        # F0の最大値・最小値を求める
        if {$opt == "all" || $opt == "f0"} {
          set f0(extractedMax) [lindex $f0(f0) 0]
          set f0(extractedMin) [lindex $f0(f0) 0]
          for {set i 1} {$i < [llength $f0(f0)]} {incr i} {
            if {$f0(extractedMax) < [lindex $f0(f0) $i]} {
              set f0(extractedMax) [lindex $f0(f0) $i]
            }
            if {$f0(extractedMin) > [lindex $f0(f0) $i] && [lindex $f0(f0) $i] > 0 ||
                $f0(extractedMin) <= 0} {
              set f0(extractedMin) [lindex $f0(f0) $i]
            }
          }
        }
        # 描画するスケールを決める
        if $f0(fixShowRange) {
          set f0(extractedMax) $f0(showMax)
          set f0(extractedMin) $f0(showMin)
          if {$f0(unit) == "semitone"} {
            if {$f0(extractedMax) > 0} { set f0(extractedMax) [hz2semitone $f0(extractedMax)] }
            if {$f0(extractedMin) > 0} { set f0(extractedMin) [hz2semitone $f0(extractedMin)] }
          }
#          } else {
#            set f0(extractedMin) $f0(min)  ;# あえてf0(min)にしている
#            if {$f0(unit) == "semitone"} {
#              if {$f0(extractedMin) > 0} { set f0(extractedMin) [hz2semitone $min] }
#            }
        }
      }
    }

    if {$f0(extractedMax) > $f0(extractedMin) && $f0(extractedMin) >= 0} {
      # ppd= 1Hzあたりのピクセル数。4は上下各2ピクセルのマージン
      set ppd [expr double($v(f0h)) / ($f0(extractedMax) - $f0(extractedMin))]

      # 各音に対応する周波数で横線を引く
      if $f0(showToneLine) {
        for {set i 0} {$i < [llength $v(sinScale)]} {incr i} {
          if {$f0(unit) == "semitone"} {
            set tgt [hz2semitone [lindex $v(sinScale) $i]]
          } else {
            set tgt [lindex $v(sinScale) $i]
          }
          set y1 [expr $ylow - ($tgt - $f0(extractedMin)) * $ppd]
          if {$y1 <= [expr $ylow - $v(f0h)]} break
          if {$y1 < $ylow} {
            $c create line 0 $y1 $cWholeWidth $y1 -tags axis \
              -fill $v(toneLineColor)
          }
        }
      }

      # ターゲット線をひく
      if $f0(showTgtLine) {
        if {$f0(unit) == "semitone"} {
          set tgt [hz2semitone $f0(tgtFreq)]
        } else {
          set tgt $f0(tgtFreq)
        }
        set y1 [expr $ylow - ($tgt - $f0(extractedMin)) * $ppd ]
        if {$y1 <= $ylow && $y1 >= [expr $ylow - $v(f0h)]} {
          $c create text [expr 0 + 2] $y1 \
            -text "$f0(tgtTone)$f0(tgtOctave)" -fill $v(tgtf0color) \
            -font smallkfont -anchor w -tags {axis tgtName}
          $c create line [lindex [$c bbox tgtName] 2] $y1 $cWholeWidth $y1 -tags axis \
            -fill $v(tgtf0color)
        }
      }

      # F0データをプロットする
      # set coord {} ;# F0曲線を引く座標(x,y)列
      set f0tags {obj f0}
      for {set i 0} {$i < [llength $f0(f0)]} {incr i} {
        # lappend coord \
        #   [expr $i * $f0(frameLength) * $v(wavepps)] \
        #   [expr $ylow - ([lindex $f0(f0) $i] - $f0(f0Min)) * $ppd]
        if {[lindex $f0(f0) $i] > 0} {
          set x1 [expr $i * $f0(frameLength) * $v(wavepps) - 2]
          set y1 [expr $ylow - ([lindex $f0(f0) $i] - $f0(extractedMin)) * $ppd - 2]
          set x2 [expr $x1 + 3]
          set y2 [expr $y1 + 3]
          if {$y1 <= $ylow && $y1 >= [expr $ylow - $v(f0h)]} {
            $c create oval $x1 $y1 $x2 $y2 -tags $f0tags -fill $v(f0color)
          }
        }
      }
    }
#      eval {$c create line} $coord -tags {$f0tags} -fill $v(f0color)
    f0Axis $cYaxis 0 $ytop $v(yaxisw) $v(f0h) \
      -tags axis -fill $v(fg) -font $v(sfont) \
      -f0Max $f0(extractedMax) -f0Min $f0(extractedMin) -unit $f0(unit)

    # 下線
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis
#    $c create line $v(yaxisw) $ytop $v(yaxisw) $ylow -tags axis -fill $v(fg)
    set yAxisLow [expr $ylow + 2]
    $cYaxis create line 0 $yAxisLow $v(yaxisw) $yAxisLow -tags axis -fill $v(fg)

    ;# F0を抽出したならFIDを記録する
    if {$f0(fid) != $v(recLab)} {
      set f0(fid) $v(recLab)
    }
  }

  ;# UTAU原音パラメータ表示
  RedrawParam

  ;# 時間軸表示
  if {$v(showWave) || $v(showSpec) || $v(showpow) || $v(showf0)} {
    set ytop [expr $v(waveh) + $v(spech) + $v(powh) + $v(f0h)]
    set ylow [expr $ytop + $v(timeh)]
    snack::timeAxis $c 0 $ytop $cWholeWidth $v(timeh) $v(wavepps) \
      -tags axis -starttime 0 -fill $v(fg)
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis
#    $c create line $v(yaxisw) $ytop $v(yaxisw) $ylow -tags axis -fill $v(fg)
  }

  ;# grabを解放
  ;#  grab release $c
}

;# パラメータ名の表示位置を求める
proc setParamNameY {} {
  global v c cYaxis power f0 paramS rec t

  set mid [expr $v(waveh) / 2]
  set kankaku [expr ($mid - $v(fontSize)) / 3]
  set v(pSy) $v(fontSize)
  set v(pOy) [expr $v(fontSize) + $kankaku]
  set v(pPy) [expr $v(fontSize) + $kankaku * 2]
  set v(pCy) $mid
  set v(pEy) $v(fontSize)
}

;# UTAU原音パラメータを描画する
proc RedrawParam {} {
  global v c cYaxis power f0 paramS rec t

  $c delete param

  set cWholeWidth [expr int($v(sndLength) * $v(wavepps))]
  setParamNameY

  ;# UTAU原音パラメータ表示
  if {$v(showParam) && $v(sndLength) > 0} {
    set ytop [expr $v(waveh) / 2]
    set ylow [expr $v(waveh) + $v(spech) + $v(powh) + $v(f0h)]
    set ls $v(listSeq)
    ;# 左ブランク
    set pSx 0
    if {[array get paramS "$ls,S"] != ""} {
      set pSx [expr $v(wavepps) * $paramS($ls,S)]
      $c create rectangle 0 0 $pSx $ylow -tags param \
        -fill #A0FFA0 -stipple gray50
      if {$v(setS) == 0} {
        $c create polygon [expr $pSx - 5] 0 [expr $pSx + 5] 0 $pSx 10 \
          -tags param -fill #00A000 ;# -stipple gray50
      }
      $c create line $pSx 0 $pSx $ylow -tags {param Sl} \
        -fill black
      $c create text $pSx $v(pSy) -text $t(Redraw,S) -fill #600060 \
        -tags {param Sl} -anchor s -font smallkfont
    }
    ;# 右ブランク
    set pEx $cWholeWidth
    if {[array get paramS "$ls,E"] != ""} {
      set pEx [expr $v(wavepps) * $paramS($ls,E)]
      $c create rectangle $cWholeWidth 0 $pEx $ylow -tags param \
        -fill #FFFFA0 -stipple gray25
      $c create line $pEx 0 $pEx $ylow -tags {param El} \
        -fill black
      $c create text $pEx $v(pEy) -text $t(Redraw,E) -fill #000060 \
        -tags {param El} -anchor s -font smallkfont
    }
    ;# オーバーラップ
    set pOx 0
    if {[array get paramS "$ls,O"] != ""} {
      set pOx [expr $v(wavepps) * $paramS($ls,O)]
      #$c create line $pSx $ylow $pOx $v(pOy) -tags {param Ol} \
      #  -fill #00FF00
      $c create line $pOx $v(pOy) $pOx $ylow -tags {param Ol} \
        -fill #00CF00
      $c create text $pOx $v(pOy) -text $t(Redraw,O) -fill #00CF00 \
        -tags {param Ol} -anchor s -font smallkfont
    }
    ;# 先行発声
    set pPx 0
    if {[array get paramS "$ls,P"] != ""} {
      set pPx [expr $v(wavepps) * $paramS($ls,P)]
      #$c create line $pSx $ylow $pPx $v(pPy) -tags {param Pl} \
      #  -fill red
      $c create line $pPx $v(pPy) $pPx $ylow -tags {param Pl} \
        -fill red
      $c create text $pPx $v(pPy) -text $t(Redraw,P) -fill red \
        -tags {param Pl} -anchor s -font smallkfont
      if {$v(setP) == 0} {
        $c create polygon [expr $pPx - 5] 0 [expr $pPx + 5] 0 $pPx 10 \
          -tags {param Pl} -fill #FF0000 ;# -stipple gray50
      }
    }
    ;# 子音部
    set pCx 0
    if {[array get paramS "$ls,C"] != ""} {
      set pCx [expr $v(wavepps) * $paramS($ls,C)]
      $c create rectangle $pSx $v(pCy) $pCx $v(waveh) -tags param \
        -fill #8080FF -stipple gray25
      $c create line $pCx $v(pCy) $pCx $ylow -tags {param Cl}\
        -fill #8080FF
      $c create text $pCx $v(pCy) -text $t(Redraw,C) -fill #8080FF \
        -tags {param Cl} -anchor s -font smallkfont
    }
  }
}

#---------------------------------------------------
# マウスx座標が波形の何μsecの所に相当するかを返す
#
proc point2micro {x} {
  global v t
  ;# ↓精度を小数点以下3桁までにする(44.1kHzのとき1sampleは0.000023秒)
  return [ \
    expr int(1000000 * $v(sndLength) * ($x - [winfo width .s]) / double($v(cWidth))) \
  ]
}

#---------------------------------------------------
# マウスx座標が波形の何secの所に相当するかを返す
#
proc point2sec {x} {
  global v t wscrl

  set cLeft  [lindex [$wscrl get] 0]
  set cLeftPoint [expr $v(sndLength) * $cLeft * $v(wavepps)]

  return [ expr double($cLeftPoint + $x) / double($v(wavepps)) ]
}

#---------------------------------------------------
# 実数を小数点以下6桁で打ち切る
#
proc cut6 {val} {
  return [expr int($val * 1000000) / 1000000.0 ]
}

#---------------------------------------------------
# 実数を小数点以下3桁で打ち切る
#
proc cut3 {val} {
  return [expr int($val * 1000) / 1000.0 ]
}

#---------------------------------------------------
# パラメータ種類名をparamU列番号に変換
#
proc kind2c {k} {
  switch $k {
    ;# A       6
    ;# fid     0
    ;#         R
    S { return 1 }
    O { return 2 }
    P { return 3 }
    C { return 4 }
    E { return 5 }
    default { return ""}
  }
}

#---------------------------------------------------
# paramUの列番号をパラメータ種類名に変換
#
proc c2kind {c} {
  switch $c {
    1 { return S }
    2 { return O }
    3 { return P }
    4 { return C }
    5 { return E }
    default { return ""}
    ;# 0       fid
    ;# 6       A
    ;# R
  }
}

#---------------------------------------------------
# 指定した行のparamUをparamSに変換する
# 波形長が$v(sndLength)に入っていることが前提
#
proc paramU2paramS {r} {
  global paramU paramS t

  for {set c 1} {$c < 6} {incr c} {
    set kindTmp [c2kind $c]
    if {[array names paramU "$r,$c"] != ""} {
      set paramS($r,$kindTmp) [u2sec $kindTmp $r $c]
    }
  }
}

#---------------------------------------------------
# 指定した行のparamSをparamUに変換する
#
proc paramS2paramU {{r -1}} {
  global paramU paramS v t

  if {$r < 0} {set r $v(listSeq)}
  for {set c 1} {$c < 6} {incr c} {
    set kind [c2kind $c]
    if {[array names paramS "$r,$kind"] != ""} {
      set paramU($r,$c) [sec2u $r $kind $paramS($r,$kind)]
    }
  }
}

#---------------------------------------------------
# マウスで原音パラメータを指定する
# 引数：kind         : パラメータの種類
#       x            : マウスのx座標、
#       changeOtherS : 1= S位置を変更した際に同じwavファイルの
#                         他の音のパラメータ位置も移動させる
#       changeOtherP : 1= P位置を変更した際に同じwavファイルの
#                         他の音のパラメータ位置も移動させる
#
proc setParam {kind x {changeOtherS 0} {changeOtherP 0}} {
  global v paramU paramUsize paramS t
  set ls $v(listSeq)
  set sec [point2sec $x]
  if {$kind != "O" && $sec < 0} {
    set sec 0
  }
  if {$kind == "S" && $v(setS) == 0} {
    ;# 設定対象がSであり、かつ他のパラメータの数値を変えない(場所は変わる)ように
    ;# する場合

    ;# Sと差を求める
    set c [kind2c $kind]
    if {[array names paramU "$ls,$c"] != ""} {
      set old $paramU($ls,$c)
    } else {
      set old 0.0
    }
    ;# Eがファイル末尾より右に行かないようにする
    if {[array names paramS "$ls,E"] != "" } {
      set Emax $v(sndLength)
      if {[expr $sec - $paramS($ls,S) + $paramS($ls,E)] > $Emax} {
        set sec [expr $Emax + $paramS($ls,S) - $paramS($ls,E)]
      }
    ;# CがEより右に行かないようにする
    } elseif {[array names paramS "$ls,C"] != "" && \
        [array names paramS "$ls,E"] != "" } {
      if {[expr $sec - $paramS($ls,S) + $paramS($ls,C)] > $paramS($ls,E)} {
        set sec [expr $paramS($ls,S) - $paramS($ls,C) +  $paramS($ls,E)]
      }
    }
    
    set paramU($ls,$c) [sec2u $ls $kind $sec]

    ;# 次に他のパラメータをUTAU形式から時刻に変換
    paramU2paramS $ls

    ;# 同wavファイルの他の音についても場所を移動させる場合
    if $changeOtherS {
      if {$paramU($ls,$c) >= $old} {
        set diff [cut3 [expr double($paramU($ls,$c) - $old) + double(0.0005)]]
      } else {
        set diff [cut3 [expr double($paramU($ls,$c) - $old) - double(0.0005)]]
      }
      _changeOtherS $ls $c $diff
    }

  } elseif {$kind == "P" && $v(setP) == 0} {
    ;# 設定対象がPであり、かつ他のパラメータの数値を変えてPとの相対的な位置関係が
    ;# 変わらないようにする場合

    ;# 旧Pとの差を求める
    set cP [kind2c $kind]
    set old 0.0
    if {[array names paramU "$ls,$cP"] != ""} { set old $paramU($ls,$cP) }
    set newPtmp [sec2u $ls $kind $sec]
    if {$newPtmp >= $old} {
      set diff [cut3 [expr double($newPtmp - $old) + double(0.0005)]]
    } else {
      set diff [cut3 [expr double($newPtmp - $old) - double(0.0005)]]
    }

    ;# Sをずらす
    set c [kind2c S]
    set oldS 0.0
    if {[array names paramU "$ls,$c"] != ""} { set oldS $paramU($ls,$c) }
    ;# Eがファイル末尾より右に行かないようにする
    set Emax $v(sndLength)
    if {[array names paramS "$ls,E"] != "" &&
          [expr $paramS($ls,E) + $diff / 1000.0] > $Emax} {
      set sec [expr $Emax + $paramS($ls,S) - $paramS($ls,E)]
      set paramU($ls,$c) [sec2u $ls S $sec]
    } else {
      ;# Sがファイル先頭より左に行かないようにする
      if {[expr $oldS + $diff] >= 0} {
        set paramU($ls,$c) [cut3 [expr $oldS + $diff]]
      } else {
        set paramU($ls,$c) 0
      }
    }

    ;# 他のパラメータをUTAU形式から時刻に変換
    paramU2paramS $ls

    ;# 同wavファイルの他の音についても場所を移動させる場合
    if $changeOtherP { _changeOtherS $ls $c $diff }

  } else {
    set paramS($ls,$kind) $sec

    ;# 設定する対象がS以外の場合、または
    ;# Sを変更しても他の位置をずらさない設定の場合の処理

    ;# Sを変えたとき
    if {$kind == "S" && [array get paramS "$ls,S"] != ""} {
      if {$paramS($ls,S) < 0} {
        ;# Sがファイル先頭より左に行かないようにする
        set paramS($ls,S) 0
      } else {
        if {$v(blankBroom)} {
          ;# Sが他のパラメータ(OはminusOの設定次第で対象外とする)より右に行ったときは他パラメータもずらす
          foreach ktmp {C E P} {
            if {[array get paramS "$ls,$ktmp"] != "" && $paramS($ls,S) > $paramS($ls,$ktmp)} {
              set paramS($ls,$ktmp)  $paramS($ls,S)
            }
          }
          if {$v(minusO) == 0 && [array get paramS "$ls,O"] != "" && $paramS($ls,S) > $paramS($ls,O)} {
            set paramS($ls,O)  $paramS($ls,S)  ;# Oは負の値にならないモードならずらす
          }
        } else {
          ;# Sが他のパラメータ(Oを除く)より右に行かないようにする
          foreach ktmp {C E P} {
            if {[array get paramS "$ls,$ktmp"] != "" && $paramS($ls,S) > $paramS($ls,$ktmp)} {
              set paramS($ls,S)  $paramS($ls,$ktmp)
            }
          }
          if {$v(minusO) == 0 && [array get paramS "$ls,O"] != "" && $paramS($ls,S) > $paramS($ls,O)} {
            set paramS($ls,S)  $paramS($ls,O)
          }
        }
      }
    ;# Eを変えたとき
    } elseif {$kind == "E" && [array get paramS "$ls,E"] != ""} {
      set Emax $v(sndLength)
      if {$paramS($ls,E) > $Emax} {
        ;# Eがファイル末尾より右に行かないようにする
        set paramS($ls,E)  $Emax
      } else {
        if {$v(blankBroom)} {
          ;# Eが他のパラメータより左に行ったときは他パラメータもずらす
          foreach ktmp {S C P O} {
            if {[array get paramS "$ls,$ktmp"] != "" && $paramS($ls,E) < $paramS($ls,$ktmp)} {
              set paramS($ls,$ktmp)  $paramS($ls,E)
            }
          }
        } else {
          ;# Eが他のパラメータより左に行かないようにする
          foreach ktmp {S C P O} {
            if {[array get paramS "$ls,$ktmp"] != "" && $paramS($ls,E) < $paramS($ls,$ktmp)} {
              set paramS($ls,E)  $paramS($ls,$ktmp)
            }
          }
        }
      }
    ;# Oを変えたとき
    } elseif {$kind == "O" && [array get paramS "$ls,O"] != ""} {
      if {$v(minusO) == 0 && [array get paramS "$ls,S"] != "" && $paramS($ls,O) < $paramS($ls,S)} {
        set paramS($ls,O) $paramS($ls,S)
      } elseif {[array get paramS "$ls,C"] != "" && $paramS($ls,O) > $paramS($ls,C)} {
        ;# パラメータがCより右に行かないようにする
        set paramS($ls,O) $paramS($ls,C)
      } elseif {[array get paramS "$ls,E"] != "" && $paramS($ls,O) > $paramS($ls,E)} {
        ;# パラメータがEより右に行かないようにする
        set paramS($ls,O) $paramS($ls,E)
      } else {
        set Emax $v(sndLength)
        if {$paramS($ls,$kind) > $Emax} {
          ;# パラメータがファイル末尾より右に行かないようにする
          set paramS($ls,$kind)  $Emax
        }
      }
    ;# Pを変えたとき
    } elseif {$kind == "P" && [array get paramS "$ls,$kind"] != ""} {
      if {$paramS($ls,$kind) < 0} {
        ;# パラメータがファイル先頭より左に行かないようにする
        set paramS($ls,$kind) 0
      } elseif {[array get paramS "$ls,S"] != "" && $paramS($ls,$kind) < $paramS($ls,S)} {
        ;# パラメータがSより左に行かないようにする
        set paramS($ls,$kind) $paramS($ls,S)
      } elseif {[array get paramS "$ls,C"] != "" && $paramS($ls,$kind) > $paramS($ls,C)} {
        ;# パラメータがCより右に行かないようにする
        set paramS($ls,$kind) $paramS($ls,C)
      } elseif {[array get paramS "$ls,E"] != "" && $paramS($ls,$kind) > $paramS($ls,E)} {
        ;# パラメータがEより右に行かないようにする
        set paramS($ls,$kind) $paramS($ls,E)
      } else {
        set Emax $v(sndLength)
        if {$paramS($ls,$kind) > $Emax} {
          ;# パラメータがファイル末尾より右に行かないようにする
          set paramS($ls,$kind)  $Emax
        }
      }
    ;# Cを変えたとき
    } elseif {$kind == "C" && [array get paramS "$ls,$kind"] != ""} {
      if {$paramS($ls,$kind) < 0} {
        ;# パラメータがファイル先頭より左に行かないようにする
        set paramS($ls,$kind) 0
      } else {
        ;# Cが他のパラメータより左に行かないようにする
        foreach ktmp {S P O} {
          if {[array get paramS "$ls,$ktmp"] != "" &&
              $paramS($ls,C) < $paramS($ls,$ktmp)} {
            set paramS($ls,C)  $paramS($ls,$ktmp)
          }
        }

        if {[array get paramS "$ls,E"] != "" && $paramS($ls,$kind) > $paramS($ls,E)} {
          ;# パラメータがEより右に行かないようにする
          set paramS($ls,$kind) $paramS($ls,E)
        } else {
          set Emax $v(sndLength)
          if {$paramS($ls,$kind) > $Emax} {
            ;# パラメータがファイル末尾より右に行かないようにする
            set paramS($ls,$kind)  $Emax
          }
        }
      }
    }

    # Uに転写
    paramS2paramU $ls
  }

  set v(paramChanged) 1
  setEPWTitle
  RedrawParam
}

#---------------------------------------------------
# alt-F1,alt-F3を押し、SやPの変更に連動して同wavの他のSやPをずらすときの処理
# ls,c = 直接変更した行,列。diff = 変更量
#
proc _changeOtherS {ls c diff} {
  global paramU paramUsize

  set fname $paramU($ls,0)

  for {set i [expr $ls - 1]} {$i >= 1 && $fname == $paramU($i,0)} {incr i -1} {
    if {[array names paramU "$i,$c"] != ""} {
      if {[expr $paramU($i,$c) + $diff] >= 0} {
        set paramU($i,$c) [cut3 [expr double($paramU($i,$c) + $diff) + double(0.0005)]]
      } else {
        set paramU($i,$c) [cut3 [expr double($paramU($i,$c) + $diff) - double(0.0005)]]
      }
      paramU2paramS $i
    }
  }
  for {set i [expr $ls + 1]} {$i < $paramUsize && $fname == $paramU($i,0)} {incr i} {
    if {[array names paramU "$i,$c"] != ""} {
      if {[expr $paramU($i,$c) + $diff] >= 0} {
        set paramU($i,$c) [cut3 [expr double($paramU($i,$c) + $diff) + double(0.0005)]]
      } else {
        set paramU($i,$c) [cut3 [expr double($paramU($i,$c) + $diff) - double(0.0005)]]
      }
      paramU2paramS $i
    }
  }
}

#---------------------------------------------------
# ファイルを保存して終了
#
proc Exit {} {
  global v t
  if $v(paramChanged) {
    set act [tk_dialog .confm $t(.confm) $t(Exit,q1) \
      question 2 $t(Exit,a1) $t(Exit,a2) $t(Exit,a3)]
    switch $act {
      0 {                      ;# 保存して終了する場合
          if ![saveParamFile] {
            return  ;# もしここで保存しなかったら終了中止。
          }
        }
      1 { }                    ;# 保存せず終了する場合
      2 { return }             ;# 終了しない場合
    }
  }

  saveSysIniFile               ;# iniファイルを保存する
  file delete $v(backupParamFile)
  exit
}

#---------------------------------------------------
# 右クリックメニュー
#
proc PopUpMenu {X Y x y} {
  global v rclickMenu rclickMenuZ rclickMenuS rclickMenuP t

  $rclickMenu delete 0 end
  $rclickMenu add cascade -label $t(PopUpMenu,zoomTitle) -menu $rclickMenuZ
  $rclickMenu add cascade -label $t(option,S) -menu $rclickMenuS
  $rclickMenu add cascade -label $t(option,P) -menu $rclickMenuP
  $rclickMenu add separator
  $rclickMenu add checkbutton -variable v(showWave) \
    -label $t(PopUpMenu,showWave) -command toggleWave
  $rclickMenu add checkbutton -variable v(showSpec) \
    -label $t(PopUpMenu,showSpec) -command toggleSpec
  $rclickMenu add checkbutton -variable v(showpow) \
    -label $t(PopUpMenu,showPow) -command togglePow
  $rclickMenu add checkbutton -variable v(showf0) \
    -label $t(PopUpMenu,showF0) -command toggleF0
  $rclickMenu add separator
  $rclickMenu add command -label $t(PopUpMenu,pitchGuide) -command pitchGuide
  $rclickMenu add command -label $t(PopUpMenu,tempoGuide) -command tempoGuide
  $rclickMenu add separator
  $rclickMenu add command -label $t(edit,estimateParam)   -command estimateParam
  $rclickMenu add command -label $t(edit,zeroCross)       -command zeroCross
  $rclickMenu add command -label $t(PopUpMenu,settings)   -command settings

  $rclickMenuZ delete 0 end
  $rclickMenuZ add command -label $t(PopUpMenu,zoom0) \
                           -command {zoomX 0     $x}
  $rclickMenuZ add command -label $t(PopUpMenu,zoom100) \
                           -command {zoomX 100   $x}
  $rclickMenuZ add command -label $t(PopUpMenu,zoom1000) \
                           -command {zoomX 1000  $x}
  $rclickMenuZ add command -label $t(PopUpMenu,zoom5000) \
                           -command {zoomX 5000  $x}
  $rclickMenuZ add command -label $t(PopUpMenu,zoom10000) \
                           -command {zoomX 10000 $x}
  $rclickMenuZ add command -label $t(PopUpMenu,zoomMax) \
                           -command {zoomX -1    $x}

  $rclickMenuS delete 0 end
  $rclickMenuS add radiobutton -label $t(option,S,1) \
    -variable v(setS) -value 0 -command {RedrawParam}
  $rclickMenuS add radiobutton -label $t(option,S,2) \
    -variable v(setS) -value 1 -command {RedrawParam}

  $rclickMenuP delete 0 end
  $rclickMenuP add radiobutton -label $t(option,P,1) \
    -variable v(setP) -value 0 -command {RedrawParam}
  $rclickMenuP add radiobutton -label $t(option,P,2) \
    -variable v(setP) -value 1 -command {RedrawParam}

  catch {tk_popup $rclickMenu $X $Y}
}

#---------------------------------------------------
# 右クリックメニュー（一覧表用）
#
proc PopUpMenuTable {X Y} {
  global v rclickMenuTable t

  $rclickMenuTable delete 0 end

  $rclickMenuTable add command -label $t(edit,tk_tableCopy) \
                               -command {tk_tableCopy .entpwindow.t}
  $rclickMenuTable add command -label $t(edit,pasteCell) \
                               -command {pasteCell    .entpwindow.t}
  $rclickMenuTable add separator
  $rclickMenuTable add command -label $t(edit,searchParam) \
                               -command searchParam
  $rclickMenuTable add command -label $t(edit,doSearchParam1) \
                               -command {doSearchParam 1}
  $rclickMenuTable add command -label $t(edit,doSearchParam0) \
                               -command {doSearchParam 0}
  $rclickMenuTable add separator
  $rclickMenuTable add command -label $t(edit,changeCell) \
                               -command changeCell
  $rclickMenuTable add command -label $t(edit,estimateParam) \
                               -command estimateParam
  $rclickMenuTable add command -label $t(edit,zeroCross) \
                               -command zeroCross
  catch {tk_popup $rclickMenuTable $X $Y}
}

#---------------------------------------------------
# バージョン情報表示
#
proc Version {} {
  global v t
  tk_messageBox -title $t(Version,msg) \
    -message "$v(appname) version $v(version)"
}

#---------------------------------------------------
# リストボックスウィジット w を d だけスクロールさせる(Windows版)
# +/-120 は Windows でホイールを1つ動かした際に%Dにセットされる値
#
proc listboxScroll {w d} {
  if {$w ne ""} {
    $w yview scroll [expr -$d / 120] units
  }
}

#---------------------------------------------------
# 単位変換
#
proc sec2samp {sec length} {
  if {[string length $sec] == 0 || [string length $length] == 0} {
    return 0
  }
  if {[string is double $sec] && [string is double $length]} {
    return [expr int(double($sec) / $length)]
  } else {
    return -1
  }
}

#---------------------------------------------------
# 単位変換(秒→UTAU原音パラメータ)
#
proc sec2u {r kind newVal} {  ;# r=-1:アクティブセルを変換。
  global paramS paramU snd v t

  if {$r < 0} {set r $v(listSeq)}  ;# 現在のアクティブセルの行番号

  if {[llength $newVal] == 0} {
    set newVal $paramS($r,$kind)   ;# 変換したい値(秒単位)を決定。
  }

  set fid $paramU($r,0)
  set fname $v(saveDir)/$fid.$v(ext)
  if {[snd cget -load] != $fname && [file readable $fname]} {
    snd read $fname
    if {[snd cget -channels] != 1} {
      set snd [snd convert -channels Mono]
    }
    set v(sndLength) [snd length -unit SECONDS]
  }
  if {[array names paramS "$r,S"] != ""} {
    set S $paramS($r,S)
  } else {
    set S 0
  }
  switch $kind {
    S { return   [cut3 [expr double($newVal) * 1000.0] ] }
    E { if {$v(setE) < 0 } {
          return [cut3 [expr - ($newVal - $S) * 1000.0] ]
        } else {
          return [cut3 [expr $v(sndLength) * 1000.0 - $newVal * 1000.0]] 
        }
      }
    C -
    P -
    O { return [cut3 [expr ($newVal - $S) * 1000] ]}
  }
}

#---------------------------------------------------
# 単位変換(UTAU原音パラメータ→秒)
# snd に波形を読んでいることが前提
#
proc u2sec {kind r c} {
  global paramS paramU t v
  if {[array names paramS "$r,S"] != ""} {
    set S $paramS($r,S)
  } else {
    set S 0
  }
  if {$paramU($r,$c) != ""} {
    set u $paramU($r,$c)
  } else {
    set u 0
  }
  switch $kind {
    S { return [cut6 [expr $u / 1000.0]] }
    C -
    P -
    O { return [cut6 [expr $u / 1000.0 + $S]] }
    E { if {$u >= 0} {
          return [cut6 [expr $v(sndLength) - $u / 1000.0]] 
        } else {
          return [cut6 [expr $u / -1000.0 + $S]]
        }
      }
  }
}

#---------------------------------------------------
# 原音パラメータ値の初期化
#
proc initParamS {} {
  global paramS v t

  array unset paramS
}

#---------------------------------------------------
# ParamUを初期化
#
proc initParamU {{clean 0} {recList {}}} {
  global paramU v paramUsize t

  if {[llength $recList] <= 0} {
    set recList $v(recList)
  }

  array unset paramU
  set paramU(0,0) $t(initParamU,0)
  set paramU(0,1) $t(initParamU,1)
  set paramU(0,2) $t(initParamU,2)
  set paramU(0,3) $t(initParamU,3)
  set paramU(0,4) $t(initParamU,4)
  set paramU(0,5) $t(initParamU,5)
  set paramU(0,6) $t(initParamU,6)
  set paramUsize 1
  set paramU(size_1) 0   ;# 表示用に行数-1した値を保存
  if $clean return  ;# もし配列サイズを0にする初期化ならここで終了
  for {set i 0} {$i < [llength $recList]} {incr i} {
    set fid [lindex $recList $i]

    ;# 表に表示するデータを設定
    set paramU($paramUsize,0) $fid

    ;# 内部で参照するデータを設定
    set paramU($paramUsize,R) $i    ;# 行番号→recListの配列番号

    incr paramUsize
  }
  ;# 一覧表のサイズを更新する
  if {$v(appname) != "OREMO" && [winfo exists .entpwindow]} {
    .entpwindow.t configure -rows $paramUsize
  }
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存
}

#---------------------------------------------------
# 一覧表のタイトルを更新する
#
proc setEPWTitle {} {
  global v t

  if $v(paramChanged) {
    set edit "(*)"
  } else {
    set edit ""
  }
  set maxlen 50
  if {[string length $v(paramFile)] <= $maxlen} {
    set fname $v(paramFile)
  } else {
    set fname [string range $v(paramFile) \
               [expr [string length $v(paramFile)] - $maxlen] end]
    set fname "...$fname"
  }
  wm title .entpwindow "$t(setEPWTitle) - $fname $edit"
}

#---------------------------------------------------
# 原音パラメータ値の一覧表を作る
#
proc makeEntParamWindow {} {
  global paramU v paramUsize t

  if [isExist .entpwindow] return  ;# 二重起動を防止

  toplevel .entpwindow
  setEPWTitle
  wm resizable .entpwindow 1 1
  wm protocol .entpwindow WM_DELETE_WINDOW Exit
  ;# bind .entpwindow <Escape> "destroy .entpwindow"

  table .entpwindow.t -variable paramU -rows $paramUsize -cols 7 \
      -colwidth 15 \
      -multiline 0 -selectmode extended \
      -titlerows 1 -titlecols 1 -selecttitle 0 \
      -colstretchmode none -rowstretchmode none \
      -padx 1 -pady 1 \
      -validate 1 -validatecommand {changeParam %s %S %r %c} \
      -xscrollcommand {.entpwindow.x set} -yscrollcommand {.entpwindow.y set}
  scrollbar .entpwindow.x -command {.entpwindow.t xview} -orient horizontal
  scrollbar .entpwindow.y -command {.entpwindow.t yview} -orient vertical

  grid .entpwindow.t .entpwindow.y -sticky news
  grid .entpwindow.x -sticky news
  grid rowconfigure .entpwindow 0 -weight 1
  grid columnconfigure .entpwindow 0 -weight 1

  .entpwindow.t width 0 10                        ;# 0列目の横幅を変更
  .entpwindow.t tag configure active -bg #A0A0A0  ;# アクティブなセルの背景色
  setCellSelection
}

#---------------------------------------------------
# 原音パラメータを数値入力で変更する
#
proc changeParam {oldValue newValue {r -1} {c -1}} {
  global paramS paramU v t
  if {$c < 0 || $r < 0} {return 1}

  set cur [.entpwindow.t icursor]   ;# 当初のカーソル位置を保存
  if {[string length $oldValue] < [string length $newValue]} {
    ;# 文字挿入の場合の新しいカーソル位置を求める
    set curNew [expr $cur + 1]
  } else {
    ;# 文字削除の場合の新しいカーソル位置を求める
    if {$cur > 0} {
      set curNew [expr $cur - 1]
      if {[string index $oldValue $curNew] == [string index $newValue $curNew]} {
        incr curNew
      }
    } else {
      set curNew 0
    }
  }

  ;# エイリアスの場合
  if {$c == 6} {
    set paramU($r,$c) $newValue
    setLabAlias
    .entpwindow.t icursor $curNew
    set v(paramChanged) 1
    setEPWTitle
    return 0   ;# ここは本来1を返すべきと思うが、1にするとセルに文字が反映されなくなった
  }

  ;# エイリアス以外のパラメータの場合
  if {[string is double $newValue] == 0 && $newValue != "-"} {  ;# 数値でなければ入力内容はボツ
    set paramU($r,$c) $oldValue
    return 0
  }
  if {$newValue != "-"} {
    set kind [c2kind $c]
    ;# 最初に0詰めされている場合は削除する(8進数と見なされるのを防ぐため)
    while {$newValue != 0 && [string index $newValue 0] == 0} {
      set newValue [string trimleft $newValue 0]
    }
    ;# 実数を入力可。小数点以下３桁以下は切り捨てる。
    if {$newValue != "" && [expr $newValue * 1000] != [expr int($newValue * 1000)]} {
      set newValue [cut3 $newValue]
    }
    set paramU($r,$c) $newValue
    set paramS($r,$kind) [u2sec $kind $r $c]

    ;# 左ブランクを変更したら、他の数値や位置を再計算させる
    if {$kind == "S"} {
      for {set column 2} {$column < 6} {incr column} {
        set kindTmp [c2kind $column]
        if $v(setS) {
          ;# 他のパラメータの位置が変わらないよう数値を再計算する
          if {[array names paramS "$r,$kindTmp"] != ""} {
            set paramU($r,$column) [sec2u $r $kindTmp $paramS($r,$kindTmp)]
          }
        } else {
          ;# 他のパラメータの数値が変わらないよう位置を再計算する
          if {[array names paramU "$r,$column"] != ""} {
            set paramS($r,$kindTmp) [u2sec $kindTmp $r $column]
          }
        }
      }
    }
  } else {
    set paramU($r,$c) $newValue
  }

  .entpwindow.t icursor $curNew
  set v(paramChanged) 1
  setEPWTitle
  RedrawParam
  return 0   ;# ここは本来1を返すべきかもしれないが、1にするとセルに文字が反映されない
}

#---------------------------------------------------
# 次のwavファイルに移動
#
proc nextWav {} {
  global v paramU paramUsize t

  set wavName $paramU($v(listSeq),0)

  set v(msg) ""
  for {set r $v(listSeq)} {[expr $r + 1] < $paramUsize} {incr r} {
    if {$paramU($r,0) != $wavName} break
  }
  if {$paramU($r,0) != $wavName} {
    setCellSelection $r $v(listC)
    jumpRec $paramU($r,R)
    # Redraw all              ;# jumpRecでRedrawされない場合もあるのでここで実行
  }
}

#---------------------------------------------------
# 前のwavファイルに移動
#
proc prevWav {} {
  global v paramU paramUsize t

  set wavName $paramU($v(listSeq),0)

  set v(msg) ""
  for {set r $v(listSeq)} {$r > 1} {incr r -1} {
    if {$paramU($r,0) != $wavName} break
  }
  if {$paramU($r,0) != $wavName} {
    setCellSelection $r $v(listC)
    jumpRec $paramU($r,R)
    # Redraw all              ;# jumpRecでRedrawされない場合もあるのでここで実行
  }
}

#---------------------------------------------------
# 次の音に移動
#
proc nextList {} {
  global v paramU paramUsize t

  set v(msg) ""
  if {$v(listSeq) < [expr $paramUsize - 1]} {
    setCellSelection [expr $v(listSeq) + 1] $v(listC)
    jumpRec $paramU($v(listSeq),R)
    # Redraw all              ;# jumpRecでRedrawされない場合もあるのでここで実行
  }
}

#---------------------------------------------------
# 前の音に移動
#
proc prevList {} {
  global v paramU paramUsize t

  set v(msg) ""
  if {$v(listSeq) > 1} {
    setCellSelection [expr $v(listSeq) - 1] $v(listC)
    jumpRec $paramU($v(listSeq),R)
    # Redraw all              ;# jumpRecでRedrawされない場合もあるのでここで実行
  }
}

#---------------------------------------------------
#
# パラメータ一覧表のセルを選択する
#
proc jumpList {w x y {mode 1} {r 0} {c 0}} {  ;# mode=1:マウスで指定。0:直接指定
  global paramU v t

  if $mode {
    ;# set sel [lindex [$w curselection] end]
    set sel [$w index "@$x,$y"]
    if {$sel == ""} return
    set r [lindex [split $sel ","] 0]
    set c [lindex [split $sel ","] 1]
  }
  if {$r < 1} return
  setCellSelection $r $c $mode
  jumpRec  $paramU($r,R)
  # Redraw all              ;# jumpRecでRedrawされない場合もあるのでここで実行
}

#---------------------------------------------------
#
# 波形画面にエイリアスを表示する
#
proc setLabAlias {} {
  global v paramU t
  if {[array names paramU $v(listSeq),6] != "" && $paramU($v(listSeq),6) != ""} {
    set v(labAlias) "($paramU($v(listSeq),6))"
  } else {
    set v(labAlias) ""
  }
}

#---------------------------------------------------
#
# パラメータ一覧表の行を複製する
#
proc duplicateEntp {w {rnow -1}} {
  global v paramU paramS paramUsize t

  if {$rnow < 0} {
    set sel [lindex [$w curselection] end]
    if {$sel == ""} return
    set rnow [lindex [split $sel ","] 0]

    ;# エラーチェック(選択領域が複数行でないか)
    if {[llength [$w curselection]] > 1} {
      foreach seltmp [$w curselection] {
        set rtmp [lindex [split $seltmp ","] 0]
        if {$rtmp != $rnow} {
          tk_messageBox -message $t(duplicateEntp,msg) \
            -title $t(duplicateEntp,title) -icon error -parent .entpwindow
          return
        }
      }
    }
  }

  ;# セル内容を一行下にコピーする
  for {set r $paramUsize} {$r > $rnow} {incr r -1} {
    set onr [expr $r - 1]
    for {set c 0} {$c < 7} {incr c} {
      if {[array names paramU $onr,$c] != ""} {
        set paramU($r,$c) $paramU($onr,$c)
      } else {
        array unset paramU $r,$c
      }
    }
    set paramU($r,R) $paramU($onr,R)

    ;# paramSの内容を一つ下にコピーする
    foreach kind {S C E P O} {
      if {[array names paramS $onr,$kind] != ""} {
        set paramS($r,$kind) $paramS($onr,$kind)
      } else {
        array unset paramS $r,$kind
      }
    }
  }
  incr paramUsize
  ;# 一覧表のサイズを更新する
  if [winfo exists .entpwindow] {
    .entpwindow.t configure -rows $paramUsize
  }
  set v(paramChanged) 1
  setEPWTitle
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存
  set v(msg) "$v(listSeq) / $paramU(size_1)"  ;# 現在何個目のデータを処理しているかを表示
}

#---------------------------------------------------
#
# パラメータ一覧表の行を削除する
#
proc deleteEntp {w {rnow ""}} {
  global v paramU paramS paramUsize t

  ;# 削除行を指定していないならフォーカスされた行を削除対象にする
  if {$rnow == ""} {
    set sel [lindex [$w curselection] 0]
    if {$sel == ""} return
    set rnow [lindex [split $sel ","] 0]   ;# 削除対象の行番号
  }

  ;# 削除する行は、duplicateEntpで複製されて複数存在するもののみとする
  ;# すなわち、あるwavに対応する行が複数ある場合のみ削除可能とし、
  ;# 対応行が一つしかない場合は削除しない。
  set del 0 ;# 1=削除可能, 0=削除不可
  for {set r [expr $rnow + 1]} {$r < $paramUsize} {incr r} {
    if {$paramU($r,R) == $paramU($rnow,R)} {
      set del 1
      break
    } elseif {$paramU($r,R) > $paramU($rnow,R)} {
      break
    }
  }
  for {set r [expr $rnow - 1]} {$r > 0} {incr r -1} {
    if {$paramU($r,R) == $paramU($rnow,R)} {
      set del 1
      break
    } elseif {$paramU($r,R) < $paramU($rnow,R)} {
      break
    }
  }
  if {$del == 0} return  ;# 削除不可なら何もしない

  ;# セル内容を一行上に移動する
  for {set r $rnow} {$r < [expr $paramUsize - 1]} {incr r} {
    for {set c 0} {$c < 7} {incr c} {
      if {[array names paramU [expr $r + 1],$c] != ""} {
        set paramU($r,$c) $paramU([expr $r + 1],$c)
      }
    }
    set paramU($r,R) $paramU([expr $r + 1],R)

    ;# paramSの内容を一つ上にコピーする
    foreach kind {S C E P O} {
      if {[array names paramS [expr $r + 1],$kind] != ""} {
        set paramS($r,$kind) $paramS([expr $r + 1],$kind)
      }
    }
  }
  ;# 最後の行を初期化する
  set r [expr $paramUsize - 1]
  for {set c 0} {$c < 7} {incr c} {
    set paramU($r,$c) ""
  }
  set paramU($r,R) ""
  foreach kind {S C E P O} {
    set paramS($r,$kind) ""
  }

  incr paramUsize -1

  ;# 一覧表のサイズを更新する
  if [winfo exists .entpwindow] {
    .entpwindow.t configure -rows $paramUsize
  }
  if {$rnow >= $paramUsize} prevList

  set v(paramChanged) 1
  setEPWTitle
  set paramU(size_1) [expr $paramUsize - 1]   ;# 表示用に行数-1した値を保存
  set v(msg) "$v(listSeq) / $paramU(size_1)"  ;# 現在何個目のデータを処理しているかを表示
}

#---------------------------------------------------
# クリップボードの内容をアクティブセルに貼り付ける
# コピーした領域が縦、横のいずれであるかや、どんな長方形領域だったかは
# 一切考慮せず、現在の選択領域に順番に貼り付けていく。
# そのため領域指定の仕方によっては値の位置が変わるので注意
#
proc pasteCell {w} {
  global paramU v t

  ;# クリップボードからデータを取得(二次元のリストで格納される)
  if {[catch {::tk::table::GetSelection $w CLIPBOARD} clip2]} return

  ;# データを一次元のリストにする
  set clip {}
  foreach l $clip2 {
    foreach d $l {
      lappend clip $d
    }
  }

  set ind 0
  foreach pos [$w curselection] {
    ;# pasteするデータを得る
    set newValue [lindex $clip $ind]
    set newValue [string trimleft  $newValue "{"]
    set newValue [string trimright $newValue "}"]

    ;# pasteする位置と現在の値を得る
    set u [split $pos ","]
    set r [lindex $u 0]
    set c [lindex $u 1]
    if {[array names paramU $pos] != ""} {
      set oldValue $paramU($pos)
    } else {
      set oldValue ""
    }
    ;# pasteする
    changeParam $oldValue $newValue $r $c

    if {[llength $clip ] >= 2} {
      incr ind
      if {$ind > [llength $clip ]} break
    }
  }
  ;# 最後にpasteしたセルをアクティブにする
  .entpwindow.t activate [lindex [$w curselection] end]
  set v(paramChanged) 1
  setEPWTitle
}

#---------------------------------------------------
# セルでカーソルを左に移動
#
proc leftInCell {w} {
  if {[$w icursor] > 0} {
    $w icursor [expr {[$w icursor] - 1}]
  } else {
    ::tk::table::MoveCell $w  0 -1
    $w icursor end
  }
}

#---------------------------------------------------
# セルでカーソルを右に移動
#
proc rightInCell {w} {
  set cur [$w icursor]
  $w icursor [expr {[$w icursor] + 1}]
  if {[$w icursor] == $cur} {
    ::tk::table::MoveCell $w  0 1
    $w icursor 0
  }
}

#---------------------------------------------------
# セル選択時の設定
#
proc setCellSelection {{r 1} {c 1} {mode 0}} {
  global v paramUsize t
  if {$mode == 0} { .entpwindow.t selection clear all }
  if {$paramUsize > $r} {
    .entpwindow.t see $r,$c
    .entpwindow.t selection set $r,$c
    .entpwindow.t activate $r,$c
    set v(listSeq) $r
    set v(listC)   $c
  }
  setLabAlias
}

#---------------------------------------------------
# フォルダ使用履歴のリストを更新する
#
proc _updateDirHistory {_dirHistory saveDir} {
  upvar $_dirHistory dirHistory
  global v startup

  # メニューの履歴表示を更新
  set seq [lsearch -exact $dirHistory $saveDir] 
  if {$seq == 0} {
    ;# 履歴に変更ないなら終了
    return
  } elseif {$seq < 0} {
    ;# 履歴にないフォルダなら追加
    set dirHistory [linsert $dirHistory 0 $saveDir]
    if {[llength $dirHistory] > $v(dirHistoryMax)} {
      set dirHistory [lrange $dirHistory 0 end-1]
    }
  } else {
    ;# 履歴にあるフォルダなら順番を変える
    set dirHistory [lreplace $dirHistory $seq $seq]
    set dirHistory [linsert  $dirHistory 0 $saveDir]
  }
}

#---------------------------------------------------
# フォルダ使用履歴を更新する
#
proc updateDirHistory {} {
  global v startup

  _updateDirHistory v(dirHistory) $v(saveDir)
  if {[regexp {^(.+)/} $v(saveDir) dummy pDir]} {
    _updateDirHistory v(pDirHistory) $pDir
  }
  renewMenu
}

#---------------------------------------------------
# iniファイルを保存する
#
proc saveSysIniFile {} {
  global v startup

  if [catch {open $startup(sysIniFile) w} fp] { 
    return
  }
  # 拡張子をファイルに保存
  puts $fp "waveFileExt=$v(ext)"
  # 起動時にダイアログを開くかどうか
  puts $fp "openStartDialog=$startup(choosesaveDir)"
  # 履歴をファイルに保存
  for {set i 0} {$i < [llength $v(dirHistory)]} {incr i} {
    puts $fp "recentDir$i=[lindex $v(dirHistory) $i]"
  }
  for {set i 0} {$i < [llength $v(pDirHistory)]} {incr i} {
    puts $fp "recentPDir$i=[lindex $v(pDirHistory) $i]"
  }
  close $fp
}

#---------------------------------------------------
# iniファイルを読む
#
proc readSysIniFile {} {
  global v startup

  set v(dirHistory) {}
  set v(pDirHistory) {}
  if [catch {open $startup(sysIniFile) r} fp] { 
    return
  }
  while {![eof $fp]} {
    set l [gets $fp]
    if {[regexp {^recentDir([0-9]+)=(.+)$} $l dummy seq dir]} {        ;# フォルダ履歴を読み込む
      set v(dirHistory) [linsert $v(dirHistory) $seq $dir]
    } elseif {[regexp {^recentPDir([0-9]+)=(.+)$} $l dummy seq dir]} { ;# 親フォルダ履歴を読み込む
      set v(pDirHistory) [linsert $v(pDirHistory) $seq $dir]
    } elseif {[regexp {^openStartDialog=(.+)$} $l dummy bool]} {       ;# 起動時のダイアログを出すかどうか
      set startup(makeRecListFromDir) $bool
      set startup(choosesaveDir) $bool
    } elseif {[regexp {^waveFileExt=(.+)$} $l dummy ext]} {            ;# 拡張子を読み込む
      set v(ext) $ext
    }
  }
  close $fp
}

#---------------------------------------------------
# 最近開いたフォルダを開く
# mode: 1=$dirを開く。 0=$dirを初期フォルダにしてユーザがフォルダ選択
#
proc openRecentDir {dir mode} {
  global v

  if $mode {
    if {[choosesaveDir 0 $dir] == 0} return
  } else {
    set v(saveDir) $dir
    if {[choosesaveDir] == 0} return
  }
  makeRecListFromDir 1 ;# 保存フォルダからリストを生成する
  resetDisplay
  setCellSelection
  updateDirHistory
}

#---------------------------------------------------
# メニュー表示内容を更新する
#
proc renewMenu {} {
  global t v
  [snack::menuGet $t(file,dirHistory)] delete 0 $v(dirHistoryMax)  ;# 履歴を消去する
  for {set i 0} {$i < [llength $v(dirHistory)]} {incr i} {
    set d [lindex $v(dirHistory) $i]
    snack::menuCommand $t(file,dirHistory) "$i: $d" "openRecentDir \"$d\" 1"
  }
  [snack::menuGet $t(file,pDirHistory)] delete 0 $v(dirHistoryMax)  ;# 履歴を消去する
  for {set i 0} {$i < [llength $v(pDirHistory)]} {incr i} {
    set d [lindex $v(pDirHistory) $i]
    snack::menuCommand $t(file,pDirHistory) "$i: $d" "openRecentDir \"$d\" 0"
  }
}

#---------------------------------------------------
# メニュー表示
#
proc setMenu {} {
  global t v

  snack::menuInit

  snack::menuPane    $t(file)
  snack::menuCommand $t(file) $t(file,choosesaveDir) {
    if [choosesaveDir] {
      makeRecListFromDir 1 ;# 保存フォルダからリストを生成する
      resetDisplay
    }
  }
  snack::menuCascade $t(file) $t(file,dirHistory)
  for {set i 0} {$i < [llength $v(dirHistory)]} {incr i} {
    set d [lindex $v(dirHistory) $i]
    snack::menuCommand $t(file,dirHistory) "$i: $d" "openRecentDir \"$d\" 1"
  }
  snack::menuCascade $t(file) $t(file,pDirHistory)
  for {set i 0} {$i < [llength $v(pDirHistory)]} {incr i} {
    set d [lindex $v(pDirHistory) $i]
    snack::menuCommand $t(file,pDirHistory) "$i: $d" "openRecentDir \"$d\" 0"
  }
  snack::menuSeparator $t(file)
  snack::menuCommand $t(file) $t(file,readParamFile) {readParamFile; resetDisplay}

  snack::menuCommand $t(file) $t(file,saveParamFile) saveParamFile
  snack::menuCommand $t(file) $t(file,readUstFile)   {readUstFile;   resetDisplay}
  snack::menuCommand $t(file) $t(file,mergeParamFile) mergeParamFile
  snack::menuSeparator $t(file)
  snack::menuCommand $t(file) $t(file,readSettings)  readSettings
  snack::menuCommand $t(file) $t(file,saveSettings)  saveSettings
  snack::menuCascade $t(file) $t(file,test)
  snack::menuCommand $t(file,test) $t(file,test,makeRecList) {
    makeRecListFromDir
    resetDisplay
    set v(msg) $t(file,test,makeRecList,msg)
  }
  snack::menuCommand $t(file,test)  $t(file,test,readRecList)  {readRecList; resetDisplay}
  snack::menuCommand $t(file,test)  $t(file,test,saveRecList)  saveRecList
  snack::menuSeparator $t(file)
  snack::menuCommand $t(file)       $t(file,Exit)              Exit

  #snack::menuPane      $t(edit)
  #snack::menuCommand   $t(edit)     $t(edit,tk_tableCopy)   {tk_tableCopy .entpwindow.t}
  #snack::menuCommand   $t(edit)     $t(edit,pasteCell)      {pasteCell    .entpwindow.t}
  #snack::menuSeparator $t(edit)
  #snack::menuCommand   $t(edit)     $t(edit,searchParam)    searchParam
  #snack::menuCommand   $t(edit)     $t(edit,doSearchParam1) {doSearchParam 1}
  #snack::menuCommand   $t(edit)     $t(edit,doSearchParam0) {doSearchParam 0}
  #snack::menuSeparator $t(edit)
  #snack::menuCommand   $t(edit)     $t(edit,changeCell) changeCell

  snack::menuPane    $t(show)
  snack::menuCascade $t(show)   $t(show,zoomTitle)
  snack::menuCommand $t(show,zoomTitle)   $t(PopUpMenu,zoom0)     {zoomX     0 0}
  snack::menuCommand $t(show,zoomTitle)   $t(PopUpMenu,zoom100)   {zoomX   100 0}
  snack::menuCommand $t(show,zoomTitle)   $t(PopUpMenu,zoom1000)  {zoomX  1000 0}
  snack::menuCommand $t(show,zoomTitle)   $t(PopUpMenu,zoom5000)  {zoomX  5000 0}
  snack::menuCommand $t(show,zoomTitle)   $t(PopUpMenu,zoom10000) {zoomX 10000 0}
  snack::menuCommand $t(show,zoomTitle)   $t(PopUpMenu,zoomMax)   {zoomX    -1 0}
  snack::menuSeparator $t(show)
  snack::menuCheck   $t(show)   $t(PopUpMenu,showWave)     v(showWave) toggleWave
  snack::menuCheck   $t(show)   $t(PopUpMenu,showSpec)     v(showSpec) toggleSpec
  snack::menuCheck   $t(show)   $t(PopUpMenu,showPow)      v(showpow)  togglePow
  snack::menuCheck   $t(show)   $t(PopUpMenu,showF0)       v(showf0)   toggleF0
  snack::menuSeparator $t(show)
  snack::menuCommand $t(show)   $t(PopUpMenu,pitchGuide)   pitchGuide
  snack::menuCommand $t(show)   $t(PopUpMenu,tempoGuide)   tempoGuide
  ;#snack::menuCommand 表示 {原音パラメータ一覧を表示}   makeEntParamWindow
  ;#snack::menuCheck   表示 {UTAU用原音パラメータを表示} v(showParam)   toggleParam

  snack::menuPane    $t(option)
  snack::menuCascade $t(option)     $t(option,gui)
  snack::menuCascade $t(option,gui) $t(option,S)
  snack::menuRadio   $t(option,S)   $t(option,S,1) v(setS) 0 {RedrawParam}
  snack::menuRadio   $t(option,S)   $t(option,S,2) v(setS) 1 {RedrawParam}
  snack::menuCascade $t(option,gui) $t(option,P)
  snack::menuRadio   $t(option,P)   $t(option,P,1) v(setP) 0 {RedrawParam}
  snack::menuRadio   $t(option,P)   $t(option,P,2) v(setP) 1 {RedrawParam}
  snack::menuCheck   $t(option,gui) $t(option,minusO)        v(minusO)        {}
  snack::menuCheck   $t(option,gui) $t(option,blankBroom)    v(blankBroom)    {}
  snack::menuCheck   $t(option,gui) $t(option,cutBlank)      v(cutBlank)      {}
  snack::menuCheck   $t(option,gui) $t(option,timingAdjMode) v(timingAdjMode) timingAdjMode

  snack::menuCascade $t(option)    $t(option,E)
  snack::menuRadio   $t(option,E)  $t(option,E,1) v(setE)  1 changeE
  snack::menuRadio   $t(option,E)  $t(option,E,2) v(setE) -1 changeE
  snack::menuSeparator $t(option)
  snack::menuCommand $t(option)    $t(option,uttTimingSettings) uttTimingSettings
  snack::menuCommand $t(option)    $t(option,setPlayRange)      setPlayRange
  snack::menuCommand $t(option)    $t(option,ioSettings)        ioSettings
  snack::menuCommand $t(option)    $t(option,settings)          settings
  #snack::menuSeparator $t(option)
  #snack::menuCascade $t(option)    $t(option,unusual)
  #snack::menuCheck   $t(option,unusual) $t(option,removeDC)     v(removeDC) {}
  #snack::menuCheck   $t(option,unusual) $t(option,rec) v(rec)   {}

  snack::menuPane    $t(tool)
  snack::menuCascade $t(tool)        $t(tool,auto)
  snack::menuCommand $t(tool,auto)   $t(tool,auto,estimateParam)       estimateParam
  snack::menuCommand $t(tool,auto)   $t(tool,auto,autoParamEstimation) autoParamEstimation
  snack::menuCommand $t(tool,auto)   $t(tool,auto,genParam)            genParam
  snack::menuCascade $t(tool)        $t(tool,wav)
  snack::menuCommand $t(tool,wav)    $t(tool,removeDCall)              removeDCall
  snack::menuCommand $t(tool,wav)    $t(tool,addUnderScore)            addUnderScore
  snack::menuCommand $t(tool,wav)    $t(tool,cutWav)                   cutWav
  snack::menuCommand $t(tool,wav)    $t(tool,convertMonoAll)           convertMonoAll
  snack::menuCascade $t(tool)        $t(tool,finish)
  snack::menuCommand $t(tool,finish) $t(tool,finish,aliasComplement)   aliasComplement
  snack::menuCommand $t(tool,finish) $t(tool,finish,woComplement)      woComplement
  snack::menuSeparator $t(tool)
  snack::menuCommand $t(tool)        $t(tool,changeAlias)              changeAlias
  snack::menuCommand $t(tool)        $t(tool,zeroCross)                zeroCross

  snack::menuPane    $t(help)
  # snack::menuCommand $t(help) $t(help,onlineHelp) {onlineHelp http://freett.com/nwp8861/soft/oremo/manual/tutorial.html}
  snack::menuCommand $t(help) $t(help,Version)    Version
}

#---------------------------------------------------
# usage
#
proc usage {} {
  global argv0 t
  puts "usage: $argv0 \[-d saveDir|-f initScript\]"
}

