#
# F0抽出用スレッド
#

#------------------------------------------------------
package require -exact snack 2.2
package require Thread

#------------------------------------------------------
# 大域変数
array unset f0
set f0(mutex) [::thread::mutex create]   ;# mutex
set f0(mainID) [tsv::get tid main]  ;# メインスレッドのID
set f0(fname) ""                         ;# F0抽出したwavファイル名
set f0(mtimeSec) 0                       ;# F0抽出したwavファイルの更新時刻
set f0(sampleRate) 44100

#------------------------------------------------------
# F0を抽出する
# force=1...抽出オプションを変更したときに強制的に再抽出させる
#
proc getF0 {fname inWavMtimeSec topdir {force 0}} {
  global f0

  ;# 排他制御用にロックをかける
  ::thread::mutex lock $f0(mutex)

  ;# 以前F0抽出したファイルと同じファイルであれば終了
  if {!$force && $fname == $f0(fname) && $inWavMtimeSec == $f0(mtimeSec)} {
    ::thread::mutex unlock $f0(mutex)
    return
  }

  ;# F0結果を初期化
  tsv::set f0 f0 {}
  tsv::set f0 status 1 ;# 1=抽出中

  ;# 抽出用パラメータを得る
  foreach key {method frameLength windowLength max min fixShowRange showMax showMin sampleRate} {
    set f0($key) [tsv::get f0 $key]
  }

  if {![file readable $fname]} {
    ::thread::mutex unlock $f0(mutex)
    return
  }

  set series {}
  set failed 0

  ;# WorldのDIO+StoneMaskによるF0抽出
  if {$f0(method) == "DIO+StoneMask"} {
    set period [expr $f0(frameLength) * 1000]
    if [catch {exec "$topdir/tools/getF0Dio.exe" -period $period -max $f0(max) -min $f0(min) $fname} ret] {
      set failed 1
      set series {}
    } else {
      set series [split $ret "\n"]
    }

  ;# SPTKのpitchコマンド(swipe)によるF0抽出
  } elseif {$f0(method) == "SPTK-SWIPE"} {
    set period [expr int($f0(frameLength) * $f0(sampleRate))]
# set ret [exec "$topdir/tools/SPTK/getF0SPTK.exe" -s $f0(sampleRate) -p $period -H $f0(max) -L $f0(min) $fname]
    if [catch {exec "$topdir/tools/getF0swipe.exe" -t 0 -p $period -H $f0(max) -L $f0(min) -o 1 $fname} ret] {
      set failed 1
      set series {}
    } else {
      set series [split $ret "\n"]
    }

  ;# pYINによるF0抽出
  } elseif {$f0(method) == "pYIN" && [file executable "$topdir/tools/getF0pYIN.exe"]} {
    set series [pYIN $fname $topdir]
    if {[llength $series] <= 0} {
      set failed 1
      set series {}
    }

  ;# snackのESPS or AMDFによるF0抽出
  } else {
    snack::sound sndF0
    sndF0 read $fname
    if {[sndF0 cget -channels] > 1} {
      sndF0 convert -channels Mono
    }
    set amp [expr [sndF0 max] - [sndF0 min]]
    if {$amp > 0} {
      sndF0 filter [snack::filter map [expr 65535.0 / $amp]]
    }
    if {[catch {set seriestmp [sndF0 pitch -method $f0(method) \
          -framelength $f0(frameLength) -windowlength $f0(windowLength) \
          -maxpitch $f0(max) -minpitch $f0(min) \
          ] } ret]} {
      set failed 1
      set seriestmp {}
    }
    if {$f0(method) == "ESPS"} {
      set series {}
      foreach s $seriestmp {
        lappend series [lindex [split $s " "] 0]
      }
    } else {
      set series $seriestmp
    }
  }

  ;# 描画用のパラメータをいくつか求める
  if {[llength $series] > 0} {
    # F0の最大値・最小値を求める
    set f0(extractedMax) [lindex $series 0]
    set f0(extractedMin) [lindex $series 0]
    foreach s $series {
      if {$f0(extractedMax) < $s} {
        set f0(extractedMax) $s
      }
      if {$f0(extractedMin) > $s && $s > 0 || $f0(extractedMin) <= 0} {
        set f0(extractedMin) $s
      }
    }
    # 描画するスケールを決める
    if $f0(fixShowRange) {
      set f0(extractedMax) $f0(showMax)
      set f0(extractedMin) $f0(showMin)
    }
  } else {
    set f0(extractedMax) 0
    set f0(extractedMin) 0
  }

  ;# 結果を返す＆記録する
  set f0(fname)    $fname
  set f0(mtimeSec) $inWavMtimeSec  ;# F0抽出対象のwavの更新日付を記録
  foreach key {extractedMax extractedMin showMax showMin} {
    tsv::set f0 $key $f0($key)
  }
  tsv::set f0 f0 $series
  if {$failed} {
    tsv::set f0 status 2    ;# 抽出エラー
  } else {
    tsv::set f0 status 0      ;# 抽出成功
  }

  ;# 描画する
  ::thread::send $f0(mainID) [list Redraw f0]

  ;# ロックを外す
  ::thread::mutex unlock $f0(mutex)
}

#------------------------------------------------------
# pYIN法でF0を抽出する
#
proc pYIN {fname topdir} {
  global f0

  ;# pYINを実行する
  if [catch {exec "$topdir/tools/getF0pYIN.exe" pyin:pyin:smoothedpitchtrack $fname} ret] {
    # puts "pYIN: always exceptions occur at here"
    return {}
  }

  ;# 結果ファイルを読みこむ
  set seriestmp {}
  set stime {}
  set sval {}
  foreach l [split $ret "\n"] {
    regsub -all -- " " $l "" l
    set data [split $l ":"]
    lappend stime [lindex $data 0]
    lappend sval  [lindex $data 1]
  }

  ;# 結果から、所定の時間間隔($f0(frameLength))でF0データを整理する
  ;# F0は直線補間で内挿する
  set seq 0
  for {set t 0} {$t < [lindex $stime end-1]} {set t [expr $t + $f0(frameLength)]} {
    for {} {[lindex $stime $seq] < $t && [expr $seq + 1] < [llength $stime]} {incr seq} {}
    set Rtime [lindex $stime $seq]
    set Rval  [lindex $sval  $seq]
    if {$seq >= 1} {
      set Ltime [lindex $stime [expr $seq - 1]]
      set Lval  [lindex $sval  [expr $seq - 1]]
    } else {
      set Ltime 0
      set Lval 0
    }
    if {$Lval > 0 && $Rval > 0} {
      set w [expr ($Rtime - $t) / ($Rtime - $Ltime)]
      lappend seriestmp [expr $Lval * $w + $Rval * (1.0 - $w)]
    } else {
      lappend seriestmp 0
    }
  }
  return $seriestmp
}

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

