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

# testとして：
#   他PCでの実行
#   空白を含むフォルダでの実行
# ペイン縦幅変更は目盛欄でのみ可能にするとか。
# DIOのスピードオプションを可変にする
# 範囲指定のアンドゥ？
# 範囲指定を次回起動時まで残す？
# mainファイルに作ったprocを整理
# 時間軸の縮尺を簡単に変更
# F0縦軸表示方法の変更
# 複数のファイル監視と表示。肉声の見本と合成の比較など。その際時間軸を補正するUIが必要。
# テンポ変更に対応した時間軸。面倒そう。
# 初回起動時のutau.exe探しの際にレジストリを見て.ustに関連付けされたアプリをutauと見なす。
# 英語版
# 歌詞を載せるとか
#
# UTAU以外向け
# - ボカロ対応：
#   　タイトル文字列の音源名が更新されない。
#   　F0のmin、maxがustから抽出できない。
#   　ボカロの縮尺に合わせる機能？バージョン違いに対応できなさそう。むしろマウスドラッグで手軽に縮尺できるようにした方が良いかも。
#
# setParam等への輸出：F0、powのスレッド化。DIO、ドラッグの粘り

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

#---------------------------------------------------
# メイン画面表示をリセットする
#
proc resetDisplay {{fname ""}} {
  global v t

  set v(recLab)  "temp"
  readWavFile $fname
  Redraw all
  setEPWTitle
}

#---------------------------------------------------
# 監視対象のwavファイル名を取得する
#
proc getWavFileName {} {
  global v t
 
  set fn [tk_getOpenFile -initialdir [file dirname $v(inWav)] -initialfile [file tail $v(inWav)] \
          -title $t(getWavFileName,title) -defaultextension "wav" \
          -filetypes { {{wav file} {.wav}} {{All Files} {*}} }]
  return $fn
}

#---------------------------------------------------
# リロードで監視対象wavファイルを開くようにする
#
proc openWavFile {fname} {
  global v t

  after cancel [after info]
  set v(inWav) $fname
  updateFileHistory
  set v(inWavMtimeSec) 0
  if {$v(reload) > 0} {
    autoReload
  } else {
    autoReload 1
  }
}

#---------------------------------------------------
# v(reload)秒ごとにwavファイルの更新をチェックする
#
proc autoReload {{byhand 0}} {
  global v t power f0 snd fTool

  if {!$byhand && $v(reload) <= 0} return

  ;# 更新日付がより新しくなっているならリロードする
  set mtime -1
  if {[file readable $v(inWav)]} {
    if {! [catch {set mtime [file mtime $v(inWav)]}]} {
      if {$mtime > $v(inWavMtimeSec) || $byhand} {
        set v(inWavMtimeSec) $mtime
        set v(inWavMtime) [clock format $mtime -format "%H:%M:%S"]
        set power(fid) ""  ;# パワーを強制的に再描画させる
        set f0(fid)    ""  ;# F0を強制的に再描画させる
        if {!$byhand} {
          $fTool.mtimeU configure -fg #ff0000 -bg #f0f0f0
          $fTool.mtime  configure -fg #ff0000 -bg #f0f0f0
        }
        readTempBat
        resetDisplay
        update
      } else {
        $fTool.mtimeU configure -fg $v(fg) -bg $v(bg)
        $fTool.mtime  configure -fg $v(fg) -bg $v(bg)
      }
    } else {
      snd flush
    }
  } else {
    snd flush
    resetDisplay
  }

  if {!$byhand} {
    after [expr int($v(reload) * 1000)] autoReload
  }
}

#---------------------------------------------------
# UTAUの画面をキャプチャして1拍分の画素数を取り、
# それと同じ縮尺になるようv(wavepps)を設定する
#
proc autoSetTimeScale {} {
  global v topdir
  if {! [file executable "$topdir/tools/getBarLength.exe"]} return
  set barLengthPixel [exec "$topdir/tools/getBarLength.exe"]
  if {$barLengthPixel <= 0} return

  # UTAUの画面に合わせて補正をかける
  switch $barLengthPixel {
    18 { set barLengthPixel 18.2666666666667 }
    21 { set barLengthPixel 21.3269230769231 }
    26 { set barLengthPixel 25.5909090909091 }
    43 { set barLengthPixel 42.6666666666667 }
  }

  changeWidth [expr - ($barLengthPixel * $v(bpm) / 60.0)] 0
  Redraw scale
}

#---------------------------------------------------
# 合成のtemp.batを読んで必要な情報を取り出す
#
proc readTempBat {} {
  global sv v f0 t

  set power(fid) ""  ;# パワーを強制的に再描画させる
  set f0(fid)    ""  ;# F0を強制的に再描画させる

  ;# batファイルが無い時の場合の初期値
  set f0(min) 80
  set f0(max) 1000
  set f0(fixShowRange) 0

  set tempBat [format "%s/temp.bat" [file dirname $v(inWav)]]
  if {! [file readable $tempBat]} return
  if [catch {open $tempBat r} in] return

  set f0(fixShowRange) 1
  set v(wavPre) 0
  set v(voice) ""
  set first 1
  set f0MaxSeq -1
  set f0MinSeq  100000
  set pbHighSeq 0
  set pbLowSeq  0
  while {![eof $in]} {
    set l [gets $in]

    if {[regexp {^(@|)set tempo=([0-9.]*)$} $l tmp tmp2 bpm]} {
      set v(bpm) $bpm
    } elseif {[regexp {^(@|)set oto=(.*)$} $l tmp tmp2 _voice]} {
      set data [split $l "\\"]
      set v(voice) [lindex $data end]
    } elseif {[regexp {%oto%\\R.wav} $l]} {
      # 休符を見つけたなら
      set first 0
    } elseif {[regexp {^(@|)set params=(.*)$} $l tmp tmp2 _params]} {
      # ピッチベンドの設定を見つけたなら、その最大値・最小値を求める
      set data [split $_params " "]
      if {[llength $data] < 4} {
        set pbHighSeq 0
        set pbLowSeq  0
      } else {
        set pbList [decodePitchBend [lindex $data 3]]
        set pbMin [getMin $pbList]   ;# 単位=cent
        set pbMax [getMax $pbList]   ;# 単位=cent
        if {[expr $pbMin % 100] == 0} {
          set pbLowSeq [expr int($pbMin / 100)]      ;# 単位=半音
        } elseif {$pbMin < 0} {
          set pbLowSeq [expr int($pbMin / 100)]      ;# 単位=半音
        } else {
          set pbLowSeq [expr int($pbMin / 100 + 1)]  ;# 単位=半音
        }
        if {[expr $pbMax % 100] == 0} {
          set pbHighSeq [expr int($pbMax / 100)]       ;# 単位=半音
        } elseif {$pbMax < 0} {
          set pbHighSeq [expr int($pbMax / 100)]       ;# 単位=半音
        } else {
          set pbHighSeq [expr int($pbMax / 100 + 1)]   ;# 単位=半音
        }
      }
    } elseif {[regexp {^(@|)call } $l]} {
      # 音符を見つけたなら
      set data0 [split $l "\""]
      set data [split [string trim [lindex $data0 2]] " "]
      if {$first} {
        set v(wavPre) [lindex $data 2]
        set first 0
      }
      regexp {^([A-Z#]+)([0-9]+)$} [lindex $data 0] tmp tone octave
      set otoSeq [expr [lsearch $sv(toneList) $tone] + $octave * 12]
      if {[expr $otoSeq + $pbLowSeq] < $f0MinSeq} {
        set f0MinSeq [expr $otoSeq + $pbLowSeq]
      }
      if {[expr $otoSeq + $pbHighSeq] > $f0MaxSeq} {
        set f0MaxSeq [expr $otoSeq + $pbHighSeq]
      }
    }
  }
  close $in

  set changed 0
  if {$f0MaxSeq >= 0} {
    set changed 1
    incr f0MaxSeq 2
    set f0(showMaxTone)   [lindex $sv(toneList) [expr $f0MaxSeq % 12]]
    set f0(showMaxOctave) [expr int($f0MaxSeq / 12)]
    if {$f0(showMaxOctave) > $sv(sinScaleMax)} {
      set f0(showMaxTone)   [lindex $sv(toneList) end]
      set f0(showMaxOctave) $sv(sinScaleMax)
    }
  }
  if {$f0MinSeq < 100000} {
    set changed 1
    incr f0MinSeq -2
    set f0(showMinTone)   [lindex $sv(toneList) [expr $f0MinSeq % 12]]
    set f0(showMinOctave) [expr int($f0MinSeq / 12)]
    if {$f0(showMinOctave) < $sv(sinScaleMin)} {
      set f0(showMinTone)   [lindex $sv(toneList) 0]
      set f0(showMinOctave) $sv(sinScaleMin)
    }
  }
  if {$changed} {
    set f0(showMin) [tone2freq "$f0(showMinTone)$f0(showMinOctave)"]
    set f0(showMax) [tone2freq "$f0(showMaxTone)$f0(showMaxOctave)"]
    set f0(min) [expr int($f0(showMin) * 0.5)]
    set f0(max) [expr int($f0(showMax) * 2.0)]
  }
}

#---------------------------------------------------
#
proc decodePitchBend {str} {
  set pbList {}
  regsub -all -- {#[0-9]+#} $str "" str     ;# ランレングス圧縮の記号を削除
  set charList [split $str ""]
  set i 0
  while {$i < [llength $charList]} {
    ;# 2文字ずつピッチベンド値(単位:cent)にデコードする
    set pb [string first [lindex $charList $i] "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]
    incr i
    set _f0 [string first [lindex $charList $i] "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]
    set pb [expr ($pb << 6) + $_f0]
    incr i
    if {[expr $pb >> 11] == 1} {
      set pb [expr -((~$pb & 4095) + 1)]   ;# 2の補数表現による負数だった場合の処理
    }
    lappend pbList $pb
  }
  return $pbList
}

#---------------------------------------------------
#
proc getMin {l} {
  set min [lindex $l 0]
  foreach i $l {
    if {$min > $i} {set min $i}
  }
  return $min
}

#---------------------------------------------------
#
proc getMax {l} {
  set max [lindex $l 0]
  foreach i $l {
    if {$max < $i} {set max $i}
  }
  return $max
}

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

  doReadInitFile $fn
}

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

  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

  ;# _SETPARAM_TOPDIR_を$topdirに置き換える
  foreach aName $startup(arrayForInitFile) {
    set sList [array get $aName]
    foreach {key value} $sList {
      if {[string first "_SETPARAM_TOPDIR_" $value] == 0} {
        set newValue [string replace $value 0 [expr [string length "_SETPARAM_TOPDIR_"] -1] $topdir]
        set [eval format "%s(%s)" $aName $key] $newValue
      }
    }
  }
}

#---------------------------------------------------
# 日本語フォントを設定する
#
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
    }
  }
}

#---------------------------------------------------
# 外部コマンドやファイル、URLを実行する
#
proc execExternal {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

  set obj [lindex $objList 0]
  set obj [file normalize $obj]
  if [file isfile $obj] {  ;# ファイルをドロップされた場合
    openWavFile $obj
  }
}

#---------------------------------------------------
# ファイルから波形を読む
# 読めなかったら1を返す
#
proc readWavFile {{_fname ""}} {
  global v snd t c

  if {$_fname == ""} {
    set fname $v(inWav)
  } else {
    set fname $_fname
  }
  if {$v(sndLength) > 0} { set v(sndLength) 0 }
  set v(inWav) $fname
  if {! [file readable $fname]} {
    return 1
  }
  if [catch {snd read $fname}] {
    tk_messageBox -message "error: can not open $fname" \
      -title $t(.confm.fioErr) -icon warning
    return 1
  }
  if {[snd cget -channels] != 1} {
    snd convert -channels Mono
  }

  if {$v(wavPre) > 0} {
    for {set i 1} {[expr 60000.0 / $v(bpm) * $i - $v(wavPre)] < 0} {incr i} {}
    set v(barPre) $i
    set v(wavOffsetSec) [expr 60.0 / $v(bpm) * $v(barPre)- $v(wavPre) / 1000.0]
    set v(bpmOffset) [expr 60.0 / $v(bpm) * $v(barPre)]
  } else {
    set v(wavOffsetSec) 0
    set v(barPre) 0
    set v(bpmOffset) 0
  }

  set v(sndLength) [expr [snd length -unit SECONDS] + $v(wavOffsetSec)]

  set waveppsOld $v(wavepps)
  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)]
  }
  $c scale regions 0 0 [expr $v(wavepps) / $waveppsOld] 1

  if {$v(wavPre) > 0} {
    set v(wavOffsetPixel) [expr $v(wavOffsetSec) * $v(wavepps)]
  } else {
    set v(wavOffsetPixel) 0
  }

  if $v(showPow) getPowerThread
  if $v(showF0) getF0Thread

  return 0
}

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

#---------------------------------------------------
# 透過率の設定窓
#
proc setAlphaWindow {} {
  global alphaWindow v t
  if [isExist $alphaWindow] return

  toplevel $alphaWindow
  wm title $alphaWindow $t(setAlphaWindow,title)
  wm attributes $alphaWindow -topmost 1 -toolwindow 1
  bind $alphaWindow <Escape> "destroy $alphaWindow"
  set topg [split [wm geometry .] "x+"]
  set x [expr ([lindex $topg 0] + [lindex $topg 2]) / 2]
  set y [expr ([lindex $topg 1] + [lindex $topg 3]) / 2]
  wm geometry $alphaWindow "+$x+$y"
  wm resizable $alphaWindow 0 0
  scale $alphaWindow.s -variable v(alpha) -orient horiz -from 0.1 -to 1 -res 0.01 -showvalue 1 -command _changeAlpha
  pack $alphaWindow.s
}
proc _changeAlpha {val} {
  wm attributes . -alpha $val
}

#---------------------------------------------------
# 更新間隔の設定窓
#
proc setReloadWindow {} {
  global reloadWindow v t fTool
  if [isExist $reloadWindow] return

  toplevel $reloadWindow
  wm title $reloadWindow $t(setReloadWindow,title)
  wm attributes $reloadWindow -topmost 1 -toolwindow 1
  bind $reloadWindow <Escape> "destroy $reloadWindow"
  set topg [split [wm geometry .] "x+"]
  set relg [split [winfo geometry $fTool.reloadT] "x+"]
  if {[lindex $topg 0] > [lindex $relg 2]} {
    set x [expr [lindex $topg 2] + [lindex $relg 2]]
    set y [expr [lindex $topg 3] + [lindex $relg 3] + 60]
  } else {
    set x [expr ([lindex $topg 0] + [lindex $topg 2]) / 2]
    set y [expr ([lindex $topg 1] + [lindex $topg 3]) / 2]
  }
  wm geometry $reloadWindow "+$x+$y"
  wm resizable $reloadWindow 0 0
  scale $reloadWindow.reloadS -variable v(reload) -orient horiz -from 0 -to 10 -res 1 -showvalue 1 -command _changeReloadS
  pack $reloadWindow.reloadS
}
proc _changeReloadS {val} {
  global reloadWindow fTool v
  if {$val == 0} {
    $reloadWindow.reloadS configure -bg #f0f0c0
    $fTool.reloadV configure -fg red
    after cancel [after info]
  } else {
    $reloadWindow.reloadS configure -bg #f0f0f0
    $fTool.reloadV configure -fg black
    if {$v(reload) > 0 && [after info] == ""} autoReload
  }
}

#---------------------------------------------------
# 拍単位の時間軸用の設定窓
#
proc setBPMWindow {} {
  global bpmWindow v t
  if [isExist $bpmWindow] return

  toplevel $bpmWindow
  wm title $bpmWindow $t(setBPMWindow,title)
  wm attributes $bpmWindow -topmost 1
  wm attributes $bpmWindow -toolwindow 1
  bind $bpmWindow <Escape> "destroy $bpmWindow"
  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 $bpmWindow "+$x+$y"
  wm resizable $bpmWindow 0 0

  set fr [frame $bpmWindow.fr]
  radiobutton $fr.rsec -text $t(show,timeAxisSec) -variable v(timeUnit) -value "Sec."
  radiobutton $fr.rbpm -text $t(show,timeAxisBar) -variable v(timeUnit) -value "Bar"
  pack $fr.rsec $fr.rbpm -side left

  set f [frame $bpmWindow.f]
  label $f.lbpm  -text $t(setBPMWindow,tempo)
  label $f.lbpm2 -text "(BPM)"
  entry $f.ebpm -textvar v(bpmTmp) -wi 10 -validate all -validatecommand {isDouble %P}
  label $f.loff -text $t(setBPMWindow,offset)
  label $f.loff2 -text $t(setBPMWindow,sec)
  entry $f.eoff -textvar v(bpmOffsetTmp) -wi 10 -validate all -validatecommand {isDouble %P}
  grid $f.lbpm  -sticky w -row 0 -column 0
  grid $f.ebpm  -sticky w -row 0 -column 1
  grid $f.lbpm2 -sticky w -row 0 -column 2
  grid $f.loff  -sticky w -row 1 -column 0
  grid $f.eoff  -sticky w -row 1 -column 1
  grid $f.loff2 -sticky w -row 1 -column 2
  button $bpmWindow.bok -text $t(.confm.ok) -command {
    if {[string is double $v(bpmTmp)]  &&
        [string length $v(bpmTmp)] > 0 && $v(bpmTmp) > 0} {
      set v(bpm) $v(bpmTmp)
    } else {
      set v(bpmTmp) $v(bpm)
    }
    if {[string length $v(bpmOffsetTmp)] <= 0} {
      set v(bpmOffsetTmp) 0
    }
    if {[string is double $v(bpmOffsetTmp)]} {
      set v(bpmOffset) $v(bpmOffsetTmp)
    } else {
      set v(bpmOffsetTmp) $v(bpmOffset)
    }
    Redraw scale
    destroy $bpmWindow
  }
  button $bpmWindow.bapply -text $t(.confm.apply) -command {
    if {[string is double $v(bpmTmp)]  &&
        [string length $v(bpmTmp)] > 0 && $v(bpmTmp) > 0} {
      set v(bpm) $v(bpmTmp)
    } else {
      set v(bpmTmp) $v(bpm)
    }
    if {[string length $v(bpmOffsetTmp)] <= 0} {
      set v(bpmOffsetTmp) 0
    }
    if {[string is double $v(bpmOffsetTmp)]} {
      set v(bpmOffset) $v(bpmOffsetTmp)
    } else {
      set v(bpmOffsetTmp) $v(bpmOffset)
    }
    Redraw scale
  }
  button $bpmWindow.bcancel -text $t(.confm.c) -command {
    set v(bpmTmp) $v(bpm)
    set v(bpmOffsetTmp) $v(bpmOffset)
    destroy $bpmWindow
  }

  pack $fr -side top
  pack $f -side top
  pack $bpmWindow.bcancel $bpmWindow.bapply $bpmWindow.bok -side right
}

#---------------------------------------------------
# 指定した周波数[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" 
  }
}

#---------------------------------------------------
# 波形を横方向に拡大縮小(ctrl+マウスホイール)
# mode=1...拡大, mode=0...縮小、負の値=その絶対値をv(wavepps)にする
#
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
  set waveppsOld $v(wavepps)

  if {$mode == 1} {
    set v(wavepps) [expr $v(wavepps) * 1.5]
  } elseif {$mode == 0} {
    set v(wavepps) [expr $v(wavepps) / 1.5]
  } elseif {$mode < 0} {
    set v(wavepps) [expr abs($mode)]
  }

  if {$mode >= 0} {
    # 描画幅がキャンバスより小さくならないようにする
    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)]
    }
  }
  if {$v(wavPre) > 0} {
    set v(wavOffsetPixel) [expr $v(wavOffsetSec) * $v(wavepps)]
  } else {
    set v(wavOffsetPixel) 0
  }
  $c scale regions 0 0 [expr double($v(wavepps)) / $waveppsOld] 1
  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
}

#---------------------------------------------------
# 時間スケールの拡大率をユーザが指定する窓
#
proc changeZoomX {} {
  global v changeZoomWindow t

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

  entry  $changeZoomWindow.erate   -textvar v(zoomRate) -wi 6 -validate key -vcmd {isDouble %P}
  label  $changeZoomWindow.lunit   -text "$t(changeZoomX,unit)"
  button $changeZoomWindow.bchange -text "$t(changeZoomX,change)" -command {
    if {$v(zoomRate) < 100}   {set v(zoomRate) 100}
    if {$v(zoomRate) > 10000} {set v(zoomRate) 10000}
    zoomX $v(zoomRate) 0
    destroy $changeZoomWindow
  }
  bind $changeZoomWindow <Return> {
    if {$v(zoomRate) < 100}   {set v(zoomRate) 100}
    if {$v(zoomRate) > 10000} {set v(zoomRate) 10000}
    zoomX $v(zoomRate) 0
    destroy $changeZoomWindow
  }

  grid $changeZoomWindow.erate   -row 0 -column 0
  grid $changeZoomWindow.lunit   -row 0 -column 1
  grid $changeZoomWindow.bchange -row 1 -column 0 -sticky nsew
}

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

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

  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 {$zoom < 0} {
    set v(zoomRate) [expr 100 * 48000 / $v(cWidth)]
  } elseif {$zoom < 100} {
    set v(zoomRate) 100
  } else {
    set v(zoomRate) $zoom
  }

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

  if {$v(wavPre) > 0} {
    set v(wavOffsetPixel) [expr $v(wavOffsetSec) * $v(wavepps)]
  } else {
    set v(wavOffsetPixel) 0
  }
  $c scale regions 0 0 [expr $v(wavepps) / $waveppsOld] 1
  Redraw scale
  $c xview moveto [expr double($sec - double($x) / $v(wavepps)) / $v(sndLength)]
}

#---------------------------------------------------
# 引数で指定した単位の値をサンプル単位に変換する
#
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 toggleOnsaPlay {} {
  global v f0 onsa t

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

#---------------------------------------------------
# 再生位置の表示
#
proc showPlayMark {{init 0}} {
  global c v
  if {$v(playStatus)} {
    set ylow [expr $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh)]
    set eTime [snack::audio elapsedTime]
    if $init {
      ;# 再生開始の場合
      after cancel showPlayMark
      $c delete playMark
      set v(pmPlayStart) [expr $v(pmPlayStart) - $eTime] ;#本来は不要のはずだが再生時にelapsedTimeが0でない場合への対策
      if {$v(pmStartDelay) > 0} {
        set x -100  ;# 表示されない位置にする
      } else {
        set x [expr $v(wavepps) * ($v(pmPlayStart) + $v(wavOffsetSec) + $eTime)]
      }
      $c create line $x 0 $x $ylow -fill #ffa030 -tags playMark
      $c raise playMark
    } else {
      ;# 再生中の場合
      if {$eTime >= $v(pmStartDelay)} {
        set x [expr $v(wavepps) * ($v(pmPlayStart) + $v(wavOffsetSec) + $eTime - $v(pmStartDelay))]
        $c coords playMark $x 0 $x $ylow
      }
    }
    update
    after 30 showPlayMark
    return
  }

  ;# 再生停止の場合
  $c delete playMark
}

#---------------------------------------------------
# 再生停止
#
proc playStop {} {
  global v snd t

  if {$v(playStatus)} {
    snd stop
    set v(playStatus) 0
  }
}

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

  if {$v(playStatus)} {
    playStop
  } else {
    set v(playStatus) 1
    snd play -start $start -end $end -command {
      set v(playStatus) 0       ;# 再生終了したときの処理
    }
    if {$start >= 0} {
      set v(pmPlayStart) [expr double($start) / $v(sampleRate)]
    } else {
      set v(pmPlayStart) 0
    }
    set v(pmStartDelay) 0
    showPlayMark 1
  }
}

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

  ;#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
  update
  set v(winHeight) [expr [winfo y $c] + $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh) + 4]
  wm geometry . "$v(winWidth)x$v(winHeight)"
}

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

  ;#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
  update
  set v(winHeight) [expr [winfo y $c] + $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh) + 4]
  wm geometry . "$v(winWidth)x$v(winHeight)"
}

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

  ;#if [snack::audio active] return
  if $v(showPow) {
      set v(powh) $v(powhbackup)
      getPowerThread
  } else {
      set v(powhbackup) $v(powh)
      set v(powh) 0
  }
  Redraw pow
  update
  set v(winHeight) [expr [winfo y $c] + $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh) + 4]
  wm geometry . "$v(winWidth)x$v(winHeight)"
}

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

  ;#if [snack::audio active] return
  if $v(showF0) {
      set v(f0h) $v(f0hbackup)
      getF0Thread
  } else {
      set v(f0hbackup) $v(f0h)
      set v(f0h) 0
  }
  Redraw f0
  update
  set v(winHeight) [expr [winfo y $c] + $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh) + 4]
  wm geometry . "$v(winWidth)x$v(winHeight)"
}

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

  if {$height <= 0} return
  if {$a(-max) <= $a(-min)} return

  # ピアノロールを表示
  set max_min [expr $a(-max) - $a(-min)]
  if {$a(-unit) == "semitone"} {
    set ylow [expr $height + $y]
    set ppd [expr double($height) / $max_min]
    set kokken  [expr int($ppd / 2)]
    set kokkenW [expr $width / 2]
    for {set i 0} {$i < [llength $sv(sinScale)]} {incr i} {
      set tgt [hz2semitone [lindex $sv(sinScale) $i]]
      set y1 [expr $ylow - ($tgt - $a(-min)) * $ppd]
      if {$y1 <= [expr $ylow - $height]} break
      if {$y1 < $ylow} {
        set tt  [expr $i % 12]
        if {$tt == 1 || $tt == 3 || $tt == 6 || $tt == 8 || $tt == 10} {
          set yt [expr $y1 - $kokken]
          set yb [expr $y1 + $kokken + 1]
          $canvas create line $kokkenW $y1 $width $y1 -tags $a(-tags) -fill black
          $canvas create rectangle 0 $yt $kokkenW $yb -tags $a(-tags) -fill #d0d0d0
        } elseif {$tt == 4 || $tt == 11} {
          set yw [expr $y1 - $kokken]
          $canvas create line 0 $yw $width $yw -tags $a(-tags) -fill black
        } elseif {$tt == 0} {
          set keyName [format "C%d" [expr int($i / 12 + 1)]]
          $canvas create text $kokkenW $y1 -text $keyName \
            -fill $a(-fill) -font $a(-font) -anchor w -tags $a(-tags)
        }
      }
    }

    return
  }

  ;# ticklist...目盛りの間隔の候補
  set ticklist [list 0.2 0.5 1 2 5 10 20 50 100 200 500]

  foreach elem $ticklist {
    set npt $elem  ;# npt...目盛の値の間隔
    ;# dy...目盛りを描画する間隔(y座標)
    set dy [expr {double($height * $npt) / $max_min}]
    if {$dy >= [font metrics $a(-font) -linespace]} break
  }
  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(-min) != 0} {
    set j0 [expr int($a(-min) / $npt) + 1]
  }

  ;# j=描画する目盛りの番号
  set yzure [expr {double($a(-min) - ($j0 - 1) * $npt) * $height / $max_min}]

  set yc [expr {$height + $y + $yzure - $i0}]  ;# 目盛を描画するy座標
  set j $j0
  for {} {$yc > $y} {set yc [expr {$yc-$dy}]; incr j} {

    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 sv t
  for {set i 0} {$i < [llength $sv(sinNote)]} {incr i} {
    if {$tone == [lindex $sv(sinNote) $i]} break
  }
  return [lindex $sv(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} {
  if {$hz <= 0} {return $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 -validate key -vcmd {isDouble %P}
  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 -validate key -vcmd {isDouble %P}
  pack $wname.lfl $wname.efl -side left
}

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

#  set dev(in)   [encoding convertfrom [lindex [snack::audio inputDevices]  0]]
  set dev(out)  [encoding convertfrom [lindex [snack::audio outputDevices] 0]]
;#puts "" ;# 手元のwish86でsnack::audio record_gainすると固まり、なぜかputsをすると先に進む現象があった
#  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]
  # snack::audio selectInput $dev(in) ;# 漢字コード未対応
}

#---------------------------------------------------
#   入出力デバイスの設定窓の値をデバイスに反映させる
#
proc setIODevice {} {
  global dev snd 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)
}

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

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

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

  foreach aName $startup(arrayForInitFile) {
    set sList [array get $aName]
    foreach {key value} $sList {
      ;# もし文字列に[が含まれていればエスケープシーケンスを挿入する
      regsub -all -- {\[} $value "\\\[" value

      ;# $topdir下のファイルを指したデータなら_SETPARAM_TOPDIR_という文字に置き換える
      ;# 他PCに移動した時などtopdirの違う環境にも対応させるため
      if {[string first $topdir $value] == 0} {
        set value [string replace $value 0 [expr [string length $topdir] - 1] "_SETPARAM_TOPDIR_"]
      }

      if {[lsearch $startup(exclusionKeysForInitFile,aName) $aName] < 0 ||
          [lsearch $startup(exclusionKeysForInitFile,$aName) $key]  < 0} {
        ;# 保存対象のデータを書き込む
        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 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]

  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 -validate key -vcmd {isDouble %P}
#  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 -validate key -vcmd {isDouble %P}
  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 -validate key -vcmd {isDouble %P}
#  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 -validate key -vcmd {isDouble %P}
#  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 -validate key -vcmd {isDouble %P}
#  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) -command {
    setIODevice
    destroy $ioswindow
  }
  button $fb.ap -text $t(.confm.apply) -command {
    setIODevice
    array set dev_bk [array get dev]     ;# パラメータバックアップ
  }
  button $fb.cn -text $t(.confm.c) -command {
    array set dev [array get dev_bk]     ;# パラメータを以前の状態に戻す
    setIODevice
    destroy $ioswindow
  }
  pack $fb.ok $fb.ap $fb.cn -side left -padx 2
  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
}

# 入力が実数なら1を返す
proc isDouble {str} {
  if {[string is double $str] && [regexp { } $str] == 0} {
    return 1
  } else {
    return 0
  }
}

#---------------------------------------------------
# プログレスバーを初期化して表示する
#
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
  if {$::tcl_platform(os) != "Darwin"} { grab set $prgWindow }
  update
}

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

  if {! [isExist $prgWindow]} return
  if {$title != ""} { wm title $prgWindow $title }
  set v(progress) $progress
  raise $prgWindow
  focus $prgWindow
  if {$::tcl_platform(os) != "Darwin"} { grab set $prgWindow }
  update
}

#---------------------------------------------------
# プログレスバーを消去する
#
proc deleteProgressWindow {} {
  global prgWindow
  if [isExist $prgWindow] {
    if {$::tcl_platform(os) != "Darwin"} { grab release $prgWindow }
    destroy $prgWindow
  }
}

#---------------------------------------------------
# エクスプローラで保存フォルダを開く
#
proc runExplorer {} {
  global v t

  if {$::tcl_platform(platform) == "windows"} {
    if {[string match $::tcl_platform(os) "Windows NT"]} {
      exec $::env(COMSPEC) /c start "" "$v(saveDir)" &
    } {
      exec start $v(saveDir) &
    }
  }
}



#---------------------------------------------------
# 詳細設定
#
proc settings {} {
  global swindow v sv power f0 v_bk power_bk f0_bk snd t topdir
    # ↑*_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]    ;# パラメータバックアップ
  set v(reloadSetting) $v(reload)   ;# 現在の更新間隔をコピーする

  ;# 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 {![isDouble %P] || ![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 {![isDouble %P] || ![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 -validate all -validatecommand {isDouble %P}
  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 -validate all -validatecommand {isDouble %P}
  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 -validate all -validatecommand {isDouble %P}
  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 -validate all -validatecommand {isDouble %P}
  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 -validate all -validatecommand {isDouble %P}
  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) DIO+StoneMask SPTK-SWIPE pYIN 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.p8cb -text $t(settings,grid) \
    -variable f0(showToneLine) -onvalue 1 -offvalue 0 -anchor w
  pack $lf4.p8cb -anchor w -fill x

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

  #;# 監視間隔
  #pack [frame $lf5.f1] -anchor w
  #label $lf5.f1.l -text $t(settings,reload) -width 20 -anchor w
  #label $lf5.f1.l2 -textvar v(reloadSetting)  -anchor e
  #scale $lf5.f1.s -variable v(reloadSetting) -orient horiz \
  #  -from 0 -to 10 -res 1 -showvalue 0
  #pack $lf5.f1.l $lf5.f1.l2 $lf5.f1.s -side left

  ;# 設定の自動保存
  pack [frame $lf5.f2] -anchor w
  checkbutton $lf5.f2.cb -text $t(settings,autoSaveInitFile) \
    -variable v(autoSaveInitFile) -onvalue 1 -offvalue 0 -anchor w
  pack $lf5.f2.cb -side left

  ;# UTAU.exeの場所指定
  pack [frame $lf5.f3] -anchor w
  label $lf5.f3.l -text $t(settings,utau) -anchor w
  label $lf5.f3.l2 -textvar v(utauExe) -anchor e
  button $lf5.f3.bu -text $t(settings,ref) -command {
    set fn ""
    set fn [tk_getOpenFile -initialdir [file dirname $v(utauExe)] -initialfile [file tail $v(utauExe)] \
          -title $t(settings,chooseUtau) -defaultextension "exe" \
          -filetypes { {{exe file} {.exe}} {{All Files} {*}} }]
    if {$fn != "" && [file executable $fn]} {
      set v(utauExe) $fn
    } else {
      tk_messageBox -title $t(.confm.errTitle) -message $t(settings,err)
    }
    raise $swindow
  }
  pack $lf5.f3.l $lf5.f3.l2 $lf5.f3.bu -side left

  ;# その他の外部波形エディタの場所設定
  pack [frame $lf5.f4] -anchor w
  label $lf5.f4.l -text $t(settings,editor) -anchor w
  label $lf5.f4.l2 -textvar v(extEditor) -anchor e
  button $lf5.f4.bu -text $t(settings,ref) -command {
    set fn ""
    set fn [tk_getOpenFile -initialdir [file dirname $v(extEditor)] -initialfile [file tail $v(extEditor)] \
          -title $t(settings,chooseEditor) -defaultextension "exe" \
          -filetypes { {{exe file} {.exe}} {{All Files} {*}} }]
    if {$fn != "" && [file executable $fn]} {
      set v(extEditor) $fn
    } else {
      tk_messageBox -title $t(.confm.errTitle) -message $t(settings,err)
    }
    raise $swindow
  }
  pack $lf5.f4.l $lf5.f4.l2 $lf5.f4.bu -side left


  ;#---------------------------
  ;# 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]    ;# パラメータを以前の状態に戻す
    setteingsReflection
    set v(reload) $v(reloadSetting)
    if {$v(reload) > 0 && [after info] == ""} autoReload
    destroy $swindow
  }
  button $frame2.f.app -text $t(.confm.apply) -command {
    setteingsReflection
    ;# パラメータバックアップの更新
    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 {
    setteingsReflection
    set v(reload) $v(reloadSetting)
    if {$v(reload) > 0 && [after info] == ""} autoReload
    destroy $swindow
  }
  pack $frame2.f.exit $frame2.f.app $frame2.f.ok -side right
}

#---------------------------------------------------
# 詳細設定のOK等のボタンを押したとき
#
proc setteingsReflection {} {
  global power f0 v
  set power(fid) ""  ;# パワーを強制的に再描画させる
  set f0(fid)    ""  ;# F0を強制的に再描画させる
  if $v(showPow) {getPowerThread 1}
  if $v(showF0) {getF0Thread 1}
  Redraw all
}

#---------------------------------------------------
# 拍を画素単位に変換
#
proc beat2pixel {beat} {
  global v

  return [expr $beat * $v(wavepps) / ($v(bpm) / 60.0) + $v(bpmOffset) * $v(wavepps)]
}

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

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

  ;# キャンバス上のものを削除して高さを再調整する
  set v(cHeight) [expr $v(waveh) + $v(spech) + $v(powh) + $v(f0h) + $v(timeh)]
  set cWholeWidth [expr int($v(sndLength) * $v(wavepps))]
  $c delete obj
  $c delete axis
  place configure $c -width [expr $v(cWidth) + 4] -height $v(cHeight)
  place configure $cYaxis -height $v(cHeight)
  place configure $wscrl -width $v(cWidth)
  $c configure -height $v(cHeight) -width $v(cWidth)
  $c configure -scrollregion [list 0 0 $cWholeWidth $v(cHeight)]
  $c configure -bg #ffffff
  $c create line 0 0 $cWholeWidth 0 -tags axis -fill $v(fg)
  set cYaxisOF 2  ;# $cと$cYaxisをgridで配置したときに位置がずれるのでこの変数で補正する
  $cYaxis delete axis
  $cYaxis configure -height $v(cHeight)
  $cYaxis create line 0 $cYaxisOF $v(yaxisw) $cYaxisOF -tags axis -fill $v(fg)
  $cYaxis create line $v(yaxisw) 0 $v(yaxisw) $v(cHeight) -tags axis -fill $v(fg)

  set ytop 0

  ;# wavファイルが無いときの処理
  if {$v(sndLength) <= 0} {
    $c create text [expr $v(cWidth) / 2 + $cWholeWidth * [lindex [$wscrl get] 0]] [expr $v(cHeight) / 2] \
        -text "wave file not found" -font v(sfont) -fill #0000a0 -anchor center -tags obj
    if {$v(reload) <= 0} {
      $c create text [expr $v(cWidth) / 2 + $cWholeWidth * [lindex [$wscrl get] 0]] [expr $v(cHeight) / 2 + 20] \
          -text "Auto reload is currently disabled." -font v(sfont) -fill #0000a0 -anchor center -tags obj
    }
    return
  }

  ;# 時間軸表示
  if {$v(sndLength) > 0} {
    set ylow [expr $ytop + $v(timeh)]
    $c create rectangle 0 $ytop $cWholeWidth $ylow -fill #d0d0d0 -tags axis
    $c create rectangle 0 $ytop [beat2pixel 0] $ylow -fill #a0b0a0 -tags axis
    if {$v(timeUnit) == "Sec."} {
      ;# 時間軸(単位=秒)
      snack::timeAxis $c [beat2pixel 0] $ytop $cWholeWidth $v(timeh) $v(wavepps) \
        -tags axis -starttime 0 -fill $v(fg)
      $c create line 0 $ylow $cWholeWidth $ylow -tags axis
    } else {
      # 小節単位の時間軸表示
      set b  [expr - $v(barPre)]  ;# 現在の拍を入れる[単位=拍]
      set bp [beat2pixel $b] ;# 現在の拍を入れる[単位=pixel]
      set onebp [expr [beat2pixel 1]    - [beat2pixel 0]] ;# 4分音符の長さ[単位=pixel]
      set bp4   [expr $onebp * 4]                         ;# 1小節の長さ[単位=pixel]
      set bp16  [expr $bp4 * 4]                           ;# 4小節の長さ[単位=pixel]
      set hbp   [expr [beat2pixel 0.5]  - [beat2pixel 0]] ;# 8分音符の長さ[単位=pixel]
      set hhbp  [expr [beat2pixel 0.25] - [beat2pixel 0]] ;# 16分音符半拍の長さ[単位=pixel]
      set vlbtmShrt [expr $ylow - $v(timeh) / 4]
      set vlbtm     [expr $ylow - $v(timeh) / 2]
      while {$bp < $cWholeWidth} {
        if {$bp16 >= 16 && [expr $b % 16] == 0} {
          ;# 4小節毎の区切り
          $c create line $bp $v(cHeight) $bp 0     -tags axis -fill #ff7d7d
          $c create text [expr $bp + 6] $vlbtm -text [expr $b / 4]  -tags axis -fill $v(fg) \
            -font $v(sfont) -anchor center
        } elseif {$bp4 >= 16 && [expr $b % 4] == 0} {
          ;# 1小節毎の縦線
          $c create line $bp $v(cHeight) $bp 0     -tags axis -fill #ff7dfc
          $c create text [expr $bp + 6] $vlbtm -text [expr $b / 4]  -tags axis -fill $v(fg) \
            -font $v(sfont) -anchor center
        } elseif {$onebp >= 16} {
          ;# 1拍毎の縦線
          $c create line $bp $v(cHeight) $bp $vlbtm     -tags axis -fill #a095ff
        }
        if {$hbp >= 16} {
          ;# 8分音符の縦線
          set bph [expr $bp + $hbp]
          $c create line $bph $ylow $bph $vlbtmShrt -tags axis -fill blue

          ;# 16分音符の縦線
          if {$hhbp >= 16} {
            set bphh [expr $bp + $hhbp]
            $c create line $bphh $ylow $bphh $vlbtmShrt -tags axis -fill #a000a0
            set bphh [expr $bp + $hhbp + $hbp]
            $c create line $bphh $ylow $bphh $vlbtmShrt -tags axis -fill #a000a0
          }
        }
        incr b
        set bp [beat2pixel $b]
      }
      incr y $v(timeh)
      $c create line 0 $ytop $cWholeWidth $ytop -tags axis -fill $v(fg)
      $c create line 0 $ylow $cWholeWidth $ylow -tags axis -fill $v(fg)
      $c raise param
    }

    ;# 時間軸ラベル
    $cYaxis create rectangle 0 [expr $ytop + $cYaxisOF] $v(yaxisw) [expr $ylow + $cYaxisOF] -fill #d0d0d0 -tags axis
    $cYaxis create text 4 [expr $ytop + $cYaxisOF + 2] -text $v(timeUnit) \
      -font $v(sfont) -anchor nw -tags axis -fill $v(fg)
  }
  set ytop [expr $ytop + $v(timeh)]

  ;# 波形表示
  if {$v(showWave)} {
    $c create waveform $v(wavOffsetPixel) $ytop -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) [expr $ytop + 4] -text $v(waveScale) \
        -font $v(sfont) -anchor ne -tags axis -fill #0000b0
      $cYaxis create text $v(yaxisw) [expr $ytop + $v(waveh) * 0.5] -text [snd max] \
        -font $v(sfont) -anchor se -tags axis -fill $v(fg)
      $cYaxis create text $v(yaxisw) [expr $ytop + $v(waveh) * 0.5 + 14] -text [snd min] \
        -font $v(sfont) -anchor se -tags axis -fill $v(fg)
    } else {
      $cYaxis create text $v(yaxisw) [expr $ytop + 4] -text [snd max] \
        -font $v(sfont) -anchor ne -tags axis -fill $v(fg)
      $cYaxis create text $v(yaxisw) [expr $ytop + $v(waveh)] -text [snd min] \
        -font $v(sfont) -anchor se -tags axis -fill $v(fg)
    }

    set ylow [expr $ytop + $v(waveh)]
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis -fill $v(fg)
    set yAxisLow [expr $ytop + $v(waveh) + $cYaxisOF]
    $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)
  }
  set ytop [expr $ytop + $v(waveh)]

  ;# スペクトル表示
  if {$v(showSpec)} {
    set ylow [expr $ytop + $v(spech)]
    if {$v(winlen) > $v(fftlen)} {
      set v(winlen) $v(fftlen)
    }
    if {$cWholeWidth < 30000} {
      $c create spectrogram $v(wavOffsetPixel) $ytop -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 $sv($v(cmap)) -topfrequency $v(topfr) \
        -pixelspersecond $v(wavepps)
    } else {
      ;# 数十秒以上のwavのスペクトルを拡大表示させる場合、フーリエ変換の回数を複数回に分けて表示する
      set sl [expr $v(sndLength) - $v(wavOffsetSec)]
      for {set sLen [expr $sl / 2.0]} {[expr $sLen * $v(wavepps)] >= 30000} {set sLen [expr $sLen / 2.0]} {}
      set sWidth [expr int($sLen * $v(wavepps) + 0.5)]
      for {set wt 0} {$wt < $sl} {set wt [expr $wt + $sLen]} {
        set wavX [expr int($v(wavepps) * $wt + $v(wavOffsetPixel) + 0.5)]
        set wavS [expr int($v(sampleRate) * $wt)]
        set wavE [expr int($v(sampleRate) * ($wt + $sLen))]
        $c create spectrogram $wavX $ytop -sound snd -height $v(spech) \
          -start $wavS -end $wavE -width $sWidth \
          -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 $sv($v(cmap)) -topfrequency $v(topfr) \
          -pixelspersecond $v(wavepps)
      }
    }
    $c lower spec
    snack::frequencyAxis $cYaxis 0 [expr $ytop + $cYaxisOF] $v(yaxisw) $v(spech) \
          -topfr $v(topfr) -tags axis -font $v(sfont)
    $c create line 0 $ylow $cWholeWidth $ylow -tags axis
    set yAxisLow [expr $ylow + $cYaxisOF]
    $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)
  }
  set ytop [expr $ytop + $v(spech)]

  ;# パワー表示
  if {$v(showPow)} {
    set ylow [expr $ytop + $v(powh)]
    ;# パワーを得る
    if {[tsv::get power status] != 0} {
      set power(power) {}   ;# 抽出中 or 抽出エラーのとき
    } else {
      foreach key {power powerMax powerMin} {
        if {[tsv::exists power $key]} {
          set power($key) [tsv::get power $key]
        }
      }
      ;# パワーを抽出したならFIDを記録する
      ;# koko, $power(fname) と $power(mtimeSec)に移行した方が良いかも
      if {$power(fid) != $v(recLab)} {
        set power(fid) $v(recLab)
      }
    }

    set pwN [llength $power(power)]
    if {$pwN > 0} {
      # ppd= 1dBあたりのピクセル数。
      if {[expr $power(powerMax) - $power(powerMin)] > 0} {
        set ppd [expr double($v(powh)) / ($power(powerMax) - $power(powerMin))]
      } else {
        set ppd 0
      }

      set pwXold $v(wavOffsetPixel)
      set pwYold [expr $ylow - ([lindex $power(power) 0] - $power(powerMin)) * $ppd]
      set a [expr $power(frameLength) * $v(wavepps)]
      for {set i 0} {$i < $pwN} {incr i} {
        set pwX [expr $i * $a + $v(wavOffsetPixel)]
        set pw  [lindex $power(power) $i]
        if {$pw > 0} {
          set pwY [expr $ylow - ($pw - $power(powerMin)) * $ppd]
        } else {
          set pwY $ylow
        }
        $c create line $pwXold $pwYold $pwX $pwY -tags {obj pow} -fill $v(powcolor)
        set pwXold $pwX
        set pwYold $pwY
      }

      # 軸表示
      myAxis $cYaxis 0 [expr $ytop + $cYaxisOF] $v(yaxisw) $v(powh) \
        -max $power(powerMax) -tags axis -fill $v(fg) \
        -font $v(sfont) -min $power(powerMin) -unit dB
    }

    $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 + $cYaxisOF]
    $cYaxis create line 0 $yAxisLow $v(yaxisw) $yAxisLow -tags axis -fill $v(fg)

    ;# 現在パワー抽出中の場合
    if {[tsv::get power status] == 1} {
      $c create rectangle 0 $ytop $cWholeWidth $ylow -fill #d0d0d0 -tags obj
      $c create text [expr $v(cWidth) / 2 + $cWholeWidth * [lindex [$wscrl get] 0]] [expr ($ytop + $ylow) / 2] \
          -text "now extracting..." -font v(sfont) -fill #0000a0 -anchor center -tags obj
    ;# 抽出エラーのとき
    } elseif {[tsv::get power status] == 2} {
      $c create text [expr $v(cWidth) / 2 + $cWholeWidth * [lindex [$wscrl get] 0]] [expr ($ytop + $ylow) / 2] \
          -text "ERROR" -font v(sfont) -fill #0000a0 -anchor center -tags obj
    }
  }
  set ytop [expr $ytop + $v(powh)]

  ;# F0表示
  if $v(showF0) {
    set ylow [expr $ytop + $v(f0h)]
    ;# F0を得る
    array set f0plot [array get f0]     ;# 現在のF0の設定値をコピーする
    if {[tsv::get f0 status] != 0} {
      set f0plot(f0) {}   ;# 抽出中 or 抽出エラーのとき
    } else {
      foreach key {f0 extractedMax extractedMin showMax showMin} {
        set f0plot($key) [tsv::get f0 $key]
      }
      if {$f0plot(unit) == "semitone"} {
        set seriestmp {}
        foreach i $f0plot(f0) {
          lappend seriestmp [hz2semitone $i]
        }
        set f0plot(f0) $seriestmp
        foreach key {extractedMax extractedMin showMax showMin} {
          set f0plot($key) [hz2semitone $f0plot($key)]
        }
      }

      ;# F0を抽出したならFIDを記録する
      ;# koko, $f0(fname) と $f0(mtimeSec)に移行した方が良いかも
      if {$f0(fid) != $v(recLab)} {
        set f0(fid) $v(recLab)
      }
    }

    if {$f0plot(extractedMax) > $f0plot(extractedMin) && $f0plot(extractedMin) >= 0} {
      # ppd= 1Hzあたりのピクセル数。4は上下各2ピクセルのマージン
      if {$f0plot(fixShowRange)} {
        set ppd [expr double($v(f0h)) / ($f0plot(showMax) - $f0plot(showMin))]
      } else {
        if {$f0plot(unit) == "semitone"} {
          set f0plot(extractedMax) [expr $f0plot(extractedMax) + 1]
          set f0plot(extractedMin) [expr $f0plot(extractedMin) - 1]
        } else {
          set f0plot(extractedMax) [expr $f0plot(extractedMax) * (13.0 / 12.0)]
          set f0plot(extractedMin) [expr $f0plot(extractedMin) / (13.0 / 12.0)]
        }
        set ppd [expr double($v(f0h)) / ($f0plot(extractedMax) - $f0plot(extractedMin))]
      }

      # 各音に対応する周波数で横線を引く
      if $f0plot(showToneLine) {
        for {set i 0} {$i < [llength $sv(sinScale)]} {incr i} {
          if {$f0plot(unit) == "semitone"} {
            set tgt [hz2semitone [lindex $sv(sinScale) $i]]
          } else {
            set tgt [lindex $sv(sinScale) $i]
          }
          set y1 [expr $ylow - ($tgt - $f0plot(extractedMin)) * $ppd]
          if {$y1 <= [expr $ylow - $v(f0h)]} break
          if {$y1 < $ylow} {
            set tt  [expr $i % 12]
            if {$tt == 1 || $tt == 3 || $tt == 6 || $tt == 8 || $tt == 10} {
              $c create line 0 $y1 $cWholeWidth $y1 -tags axis -fill #50c0f0 -stipple gray50
            } else {
              $c create line 0 $y1 $cWholeWidth $y1 -tags axis -fill #f0c0a0
            }
          }
        }
      }

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

    ;# 現在F0抽出中の場合
    if {[tsv::get f0 status] == 1} {
      $c create rectangle 0 $ytop $cWholeWidth $ylow -fill #d0d0d0 -tags obj
      $c create text [expr $v(cWidth) / 2 + $cWholeWidth * [lindex [$wscrl get] 0]] [expr ($ytop + $ylow) / 2] \
          -text "now extracting..." -font v(sfont) -fill #0000a0 -anchor center -tags obj
    ;# 抽出エラーの場合
    } elseif {[tsv::get f0 status] == 2} {
      $c create text [expr $v(cWidth) / 2 + $cWholeWidth * [lindex [$wscrl get] 0]] [expr ($ytop + $ylow) / 2] \
          -text "ERROR" -font v(sfont) -fill #0000a0 -anchor center -tags obj
    }
  }
  set ytop [expr $ytop + $v(f0h)]

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

#---------------------------------------------------
# パワーの抽出(別スレッドで)
#
proc getPowerThread {{force 0}} {
  global v power

  foreach key {frameLength window preemphasis windowLength} {
    tsv::set power $key $power($key)
  }
  tsv::set power sampleRate $v(sampleRate)
  ::thread::send -async [tsv::get tid power] "getPower $v(inWav) $v(inWavMtimeSec) $force"
}

#---------------------------------------------------
# F0の抽出(別スレッドで)
#
proc getF0Thread {{force 0}} {
  global v f0 topdir

  foreach key {method frameLength windowLength max min fixShowRange showMax showMin} {
    tsv::set f0 $key $f0($key)
  }
  tsv::set f0 sampleRate $v(sampleRate)
  ::thread::send -async [tsv::get tid f0] "getF0 $v(inWav) $v(inWavMtimeSec) $topdir $force"
}

#---------------------------------------------------
# マウス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 ]
}

#---------------------------------------------------
# ファイルを保存して終了
#
proc Exit {} {
  global v t startup
  saveSysIniFile               ;# iniファイルを保存する
  if {$v(autoSaveInitFile)} {
    saveSettings $startup(initFile)
  }
  catch {::thread::send -async [tsv::get tid power] "::thread::unwind"}
  catch {::thread::send -async [tsv::get tid f0] "::thread::unwind"}
  destroy .
  exit
}

#---------------------------------------------------
# 選択範囲をすべて削除する
#
proc clearRegion {} {
  global c
  $c delete regions
}

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

  $rclickMenu delete 0 end
  $rclickMenu add cascade -label $t(PopUpMenu,zoomTitle) -menu $rclickMenuZ
  $rclickMenu add separator
  $rclickMenu add cascade -label $t(PopUpMenu,gridTitle) -menu $rclickMenuG
  $rclickMenu add command -label $t(PopUpMenu,clearRegion) -command clearRegion
  $rclickMenu add separator
  $rclickMenu add cascade -label $t(PopUpMenu,paneTitle) -menu $rclickMenuP
  $rclickMenu add separator
  $rclickMenu add command -label $t(PopUpMenu,settings)   -command settings

  $rclickMenuP delete 0 end
  $rclickMenuP add checkbutton -variable v(showWave) -label $t(PopUpMenu,showWave) -command toggleWave
  $rclickMenuP add checkbutton -variable v(showSpec) -label $t(PopUpMenu,showSpec) -command toggleSpec
  $rclickMenuP add checkbutton -variable v(showPow)  -label $t(PopUpMenu,showPow) -command togglePow
  $rclickMenuP add checkbutton -variable v(showF0)   -label $t(PopUpMenu,showF0) -command toggleF0

  $rclickMenuZ delete 0 end
  $rclickMenuZ add command -label $t(PopUpMenu,zoomUTAU)  -command "autoSetTimeScale"
  $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"
  $rclickMenuZ add command -label $t(PopUpMenu,changeZoomX) -command {changeZoomX}

  $rclickMenuG delete 0 end
  $rclickMenuG add radiobutton -variable v(gridNote) -value  1 -label $t(PopUpMenu,grid1)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  2 -label $t(PopUpMenu,grid2)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  4 -label $t(PopUpMenu,grid4)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  6 -label $t(PopUpMenu,grid6)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  8 -label $t(PopUpMenu,grid8)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 12 -label $t(PopUpMenu,grid12)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 16 -label $t(PopUpMenu,grid16)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 24 -label $t(PopUpMenu,grid24)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 32 -label $t(PopUpMenu,grid32)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  0 -label $t(PopUpMenu,grid0)

  catch {tk_popup $rclickMenu $X $Y}
}

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

#---------------------------------------------------
# 単位変換
#
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
  }
}

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

  if {[regexp {^.*/(utau[1-9]+)/} $v(inWav) dummy utaudir]} {
    regsub {^.*/utau[1-9]+/} $v(inWav) "$utaudir/" inWav
  } else {
    regsub {[a-z]:/Users/} $v(inWav) "~" inWav
  }
  set inWav "$inWav ($v(inWavMtime), $v(voice))"

  set maxlen 50
  if {[string length $inWav] <= $maxlen} {
    set fname $inWav
  } else {
    set fname [string range $inWav \
               [expr [string length $inWav] - $maxlen] end]
    set fname "...$fname"
  }
  wm title . "$sv(appname) $sv(version) - $fname"
}

#---------------------------------------------------
# ファイル使用履歴のリストを更新する
#
proc _updateFileHistory {_fileHistory inWav} {
  upvar $_fileHistory fileHistory
  global v startup

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

#---------------------------------------------------
# ファイル使用履歴を更新する
#
proc updateFileHistory {} {
  global v startup

  _updateFileHistory v(fileHistory) $v(inWav)
  if {[regexp {^(.+)/} $v(inWav) dummy pDir]} {
    _updateFileHistory v(pDirHistory) $pDir
  }
  renewMenu
}

#---------------------------------------------------
# $v(appname)-settings.iniファイルを保存する
#
proc saveSysIniFile {} {
  global v startup

  if [catch {open $startup(sysIniFile) w} fp] {
    return
  }
  puts $fp "utauExe=$v(utauExe)  ; UTAUのexeファイル"
  puts $fp "inWav=$v(inWav)  ; 監視対象wavファイル"
  puts $fp "reload=$v(reload)  ; 監視する間隔（単位は秒）"
  puts $fp "autoSaveInitFile=$v(autoSaveInitFile)  ; 起動時に前回終了時の状態を復元する"
  # 履歴をファイルに保存
  for {set i 0} {$i < [llength $v(fileHistory)]} {incr i} {
    puts $fp "fileHistory$i=[lindex $v(fileHistory) $i]"
  }
  for {set i 0} {$i < [llength $v(pDirHistory)]} {incr i} {
    puts $fp "pDirHistory$i=[lindex $v(pDirHistory) $i]"
  }
  close $fp
}

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

  set v(fileHistory) {}
  set v(pDirHistory) {}
  if [catch {open $startup(sysIniFile) r} fp] { 
    return
  }
  while {![eof $fp]} {
    set l [gets $fp]
    regexp {^([^;]*);.*$} $l dummy l  ;# コメントを削除する
    set l [string trim $l]
    if {[regexp {^utauExe *= *(.+)$} $l dummy utauExe]} {
      set v(utauExe) $utauExe
    } elseif {[regexp {^inWav *= *(.+)$} $l dummy inWav]} {
      set v(inWav) $inWav
    } elseif {[regexp {^reload *= *(.+)$} $l dummy reload]} {
      set v(reload) $reload
    } elseif {[regexp {^autoSaveInitFile *= *(.+)$} $l dummy autoSaveInitFile]} {
      set v(autoSaveInitFile) $autoSaveInitFile
    } elseif {[regexp {^fileHistory([0-9]+) *= *(.+)$} $l dummy seq rfile]} { ;# ファイル履歴を読み込む
      set v(fileHistory) [linsert $v(fileHistory) $seq $rfile]
    } elseif {[regexp {^pDirHistory([0-9]+) *= *(.+)$} $l dummy seq dir]} { ;# 親フォルダ履歴を読み込む
      set v(pDirHistory) [linsert $v(pDirHistory) $seq $dir]
    }
  }
  close $fp
}

#---------------------------------------------------
# 最近開いたファイルを開く
#
proc openRecentFile {fname} {
  global v t

  if [file isdirectory $fname] {
    ;# $fnameにディレクトリが入ってきた場合
    set initdir $fname
    set initfile "temp.wav"
    set fn [tk_getOpenFile -initialdir $initdir -initialfile $initfile \
            -title $t(file,getWavFileName) -defaultextension "wav" \
            -filetypes { {{WAV file} {.wav}} {{All Files} {*}} }]
    if {$fn == ""} return
    set fname $fn
  }
  openWavFile $fname
}

#---------------------------------------------------
# メニュー表示内容を更新する
#
proc renewMenu {} {
  global t v clickMenuFileC clickMenuFileC2

  $clickMenuFileC delete 0 end
  for {set i 0} {$i < [llength $v(fileHistory)]} {incr i} {
    set d [lindex $v(fileHistory) $i]
    $clickMenuFileC add command -label "$i: $d" -command "openRecentFile \"$d\""
  }
  $clickMenuFileC2 delete 0 end
  for {set i 0} {$i < [llength $v(pDirHistory)]} {incr i} {
    set d [lindex $v(pDirHistory) $i]
    $clickMenuFileC2 add command -label "$i: $d" -command "openRecentFile \"$d\""
  }
}

#---------------------------------------------------
# ファイルメニュー
#
proc PopUpMenuFile {X Y} {
  global v clickMenuFile clickMenuFileC clickMenuFileC2 t

  $clickMenuFile delete 0 end
  $clickMenuFile add command -label $t(file,getWavFileName) -command {
    set fname [getWavFileName]
    if {$fname != ""} {
      openWavFile $fname
    }
  }
  $clickMenuFile add cascade -label $t(file,fileHistory) -menu $clickMenuFileC
  $clickMenuFile add cascade -label $t(file,pDirHistory) -menu $clickMenuFileC2
  $clickMenuFile add separator
  $clickMenuFile add command -label $t(file,readSettings) -command readSettings
  $clickMenuFile add command -label $t(file,saveSettings) -command saveSettings
  $clickMenuFile add separator
  $clickMenuFile add command -label $t(file,Exit)         -command Exit

  $clickMenuFileC delete 0 end
  for {set i 0} {$i < [llength $v(fileHistory)]} {incr i} {
    set d [lindex $v(fileHistory) $i]
    $clickMenuFileC add command -label "$i: $d" -command "openRecentFile \"$d\""
  }

  $clickMenuFileC2 delete 0 end
  for {set i 0} {$i < [llength $v(pDirHistory)]} {incr i} {
    set d [lindex $v(pDirHistory) $i]
    $clickMenuFileC2 add command -label "$i: $d" -command "openRecentFile \"$d\""
  }

  catch {tk_popup $clickMenuFile $X $Y}
}

#---------------------------------------------------
# 表示メニュー
#
proc PopUpMenuShow {X Y} {
  global v clickMenuShow clickMenuShowC clickMenuShowC2 t

  $clickMenuShow delete 0 end
  $clickMenuShow add cascade -label $t(show,zoomTitle) -menu $clickMenuShowC
  $clickMenuShow add cascade -label $t(show,timeAxis)  -menu $clickMenuShowC2
  $clickMenuShow add separator
  $clickMenuShow add checkbutton -label $t(PopUpMenu,showWave) -variable v(showWave) -command toggleWave
  $clickMenuShow add checkbutton -label $t(PopUpMenu,showSpec) -variable v(showSpec) -command toggleSpec
  $clickMenuShow add checkbutton -label $t(PopUpMenu,showPow)  -variable v(showPow)  -command togglePow
  $clickMenuShow add checkbutton -label $t(PopUpMenu,showF0)   -variable v(showF0)   -command toggleF0
  $clickMenuShow add separator
  $clickMenuShow add checkbutton -label $t(PopUpMenu,alwaysOnTop) -variable v(alwaysOnTop) -command {
    if {$v(alwaysOnTop)} {
      wm attributes . -topmost 1
    } else {
      wm attributes . -topmost 0
    }
  }
  $clickMenuShow add command -label $t(PopUpMenu,setAlpha) -command setAlphaWindow

  $clickMenuShowC delete 0 end
  $clickMenuShowC add command -label $t(PopUpMenu,zoomUTAU)  -command "autoSetTimeScale"
  $clickMenuShowC add command -label $t(PopUpMenu,zoom0)     -command {zoomX     0 0}
  $clickMenuShowC add command -label $t(PopUpMenu,zoom100)   -command {zoomX   100 0}
  $clickMenuShowC add command -label $t(PopUpMenu,zoom1000)  -command {zoomX  1000 0}
  $clickMenuShowC add command -label $t(PopUpMenu,zoom5000)  -command {zoomX  5000 0}
  $clickMenuShowC add command -label $t(PopUpMenu,zoom10000) -command {zoomX 10000 0}
  $clickMenuShowC add command -label $t(PopUpMenu,zoomMax)   -command {zoomX    -1 0}
  $clickMenuShowC add command -label $t(PopUpMenu,changeZoomX) -command {changeZoomX}

  $clickMenuShowC2 delete 0 end
  $clickMenuShowC2 add radiobutton -label $t(show,timeAxisSec) -variable v(timeUnit) -value "Sec." -command { Redraw scale }
  $clickMenuShowC2 add radiobutton -label $t(show,timeAxisBar) -variable v(timeUnit) -value "Bar"  -command { Redraw scale ; setBPMWindow }
  $clickMenuShowC2 add separator
  $clickMenuShowC2 add command -label $t(setBPMWindow,title) -command setBPMWindow

  catch {tk_popup $clickMenuShow $X $Y}
}

#---------------------------------------------------
# オプションメニュー
#
proc PopUpMenuOptn {X Y} {
  global v clickMenuOptn rclickMenuG t

  $clickMenuOptn delete 0 end
  $clickMenuOptn add cascade -label $t(PopUpMenu,gridTitle) -menu $rclickMenuG
  $clickMenuOptn add command -label $t(PopUpMenu,reload)    -command setReloadWindow
  $clickMenuOptn add command -label $t(option,ioSettings)   -command ioSettings
  $clickMenuOptn add command -label $t(option,settings)     -command settings

  $rclickMenuG delete 0 end
  $rclickMenuG add radiobutton -variable v(gridNote) -value  1 -label $t(PopUpMenu,grid1)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  2 -label $t(PopUpMenu,grid2)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  4 -label $t(PopUpMenu,grid4)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  6 -label $t(PopUpMenu,grid6)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  8 -label $t(PopUpMenu,grid8)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 12 -label $t(PopUpMenu,grid12)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 16 -label $t(PopUpMenu,grid16)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 24 -label $t(PopUpMenu,grid24)
  $rclickMenuG add radiobutton -variable v(gridNote) -value 32 -label $t(PopUpMenu,grid32)
  $rclickMenuG add radiobutton -variable v(gridNote) -value  0 -label $t(PopUpMenu,grid0)

  catch {tk_popup $clickMenuOptn $X $Y}
}

#---------------------------------------------------
# ヘルプメニュー
#
proc PopUpMenuHelp {X Y} {
  global v clickMenuHelp t

  $clickMenuHelp delete 0 end
  $clickMenuHelp add command -label $t(help,Version)   -command Version
  $clickMenuHelp add command -label $t(help,official1) -command {execExternal http://freett.com/nwp8861/soft/utawaview/}
  $clickMenuHelp add command -label $t(help,official2) -command {execExternal http://sourceforge.jp/users/nwp8861/pf/utawaview/files/}

  catch {tk_popup $clickMenuHelp $X $Y}
}

#---------------------------------------------------
# usage
#
proc usage {} {
  global argv0 t
  puts "usage: $argv0 "
}

