Snackでプレーヤーを書く

 Snackは、 Tcl、Pythonで音声ファイルの作成、再生を行うコマンドを追加するライブラリです。 対応する音声フォーマットはAU、AIFF、WAV、MP3などがあり、 簡単な再生アプリケーションをすぐに作ることができます。 現在のバージョンは2.2です。 実験室ではこのバージョンのバイナリ配布をWindows XP Home Edition、 ActiveTcl 8.4.1と8.4.6で使用したので、この環境をもとに紹介します。

インストール
 Windows向けには上記サイトからバイナリアーカイブが配布されています。 インストーラがついていて、Snack専用のフォルダにサンプルや文書等がコピーされるほか、 先にインストールされているTcl処理系のライブラリの中にSnackの設定が追加されるようです。

最も簡単なサンプル

package require snack

button .ba -text "Play" -command playSound
pack .ba -side top

proc playSound {} {
  set s [snack::sound s]
  $s read "abc.mp3"
  $s play
}

まずはボタンを押したら指定した音声ファイルを再生するだけのサンプルです。 Snackを使うには
package require snack
でライブラリをロードすることを指示します。で、実際の処理は
  1. snack::soundコマンドで「音声ハンドル」を作る。
  2. 音声ハンドルのreadコマンドで音声ファイルを読み込む。(※1)
  3. 音声ハンドルのplayコマンドで再生を開始する。
と順番に行えばOKです。
(※1) または、音声ハンドルの-fileオプションを指定する(configureで) ことでも音声ファイルをロードできます。

Snackでプレーヤーを書く
 それではSnackの仕組みを使って、音声ファイルプレーヤーを作ってみます。

package require snack
package require tkdnd

set initialDir .

message .ma -text "(no file specified)" -bg "#400000" -fg white -width 400
set f [frame .fa -bg #600000]
button $f.bplay  -text "再生"     -command playSound
button $f.bpause -text "一時停止" -command pauseSound
button $f.bstop  -text "停止"     -command stopSound
pack $f.bplay $f.bpause $f.bstop -side left -padx 5 -pady 5
foreach b {bplay bpause bstop} {$f.$b configure -state disabled}
pack .ma -side top -padx 2 -pady 2 -fill x
pack .fa -side top -padx 2 -pady 2

set m [menu .m]
set mfile [menu $m.mfile]
$mfile add command -label "開く..." -command openSoundFile
$mfile add separator
$mfile add command -label "終了" -command exit
$m add cascade -menu $mfile -label "ファイル"
. configure -menu $m

dnd bindtarget .ma text/uri-list <Drop> {dropped %D}

proc openSoundFile {{filename {}}} {
  global currentSound paused initialDir
  if {"$filename" == ""} {
    set filename [tk_getOpenFile -initialdir $initialDir]
    if {"$filename" == ""} return
  }
  set initialDir [file dirname $filename]

  .fa configure -cursor watch; update
  .ma configure -cursor watch; update
  .ma configure -text [file tail $filename]
  set currentSound [snack::sound s]
  $currentSound read $filename
  .fa configure -cursor {}; update
  .ma configure -cursor {}; update

  set paused 0
  foreach b {bplay bpause bstop} {.fa.$b configure -state normal}
}

proc dropped filenames {
  set filename [lindex $filenames 0]
  after 100 openSoundFile \"$filename\"
}

proc playSound {} {
  global currentSound
  $currentSound play
}

proc stopSound {} {
  global currentSound
  $currentSound stop
}

proc pauseSound {} {
  global currentSound paused
  if $paused {
    set paused 0; .fa.bpause configure -text "一時停止"
  } else {
    set paused 1; .fa.bpause configure -text "再開"
  }
  $currentSound pause
}
# end.

スクリーンショット

 上のスクリプトでは、 音声ファイルをウィンドウにドラッグ&ドロップで開くことができるように、 tkdndも使っています。 tkdndがなくて、普通にメニューから開くだけでいい場合には、 tkdndに関係するところを削っても動きます。
 で、この例ではプレーヤーらしく、再生の他に一時停止、停止のボタンがついています。 一時停止と再開はともに音声ハンドルの pauseで行います。(呼ぶ度に切り替わります) 停止は同じくstopサブコマンドで行えます。

繰り返し再生をさせるには
 音声ハンドルのplayサブコマンドには -commandというオプションがあって、 これにコマンドやTclスクリプト(コールバックスクリプト)を指定しておくと、 その再生が終了したときに実行されます。 これを利用すると、 再生が終了した際のコールバックスクリプトでもう一度再生を行わせることで、 繰り返し再生ができるわけです。

package require snack
package require tkdnd

set initialDir .

message .ma -text "(no file specified)" -bg "#400000" -fg white -width 400
set f [frame .fa -bg #600000]
button $f.bplay  -text "再生"     -command playSound
button $f.bpause -text "一時停止" -command pauseSound
button $f.bstop  -text "停止"     -command stopSound
checkbutton $f.chkrepeat -text "繰り返し" -variable repeat; set repeat 0
pack $f.bplay $f.bpause $f.bstop $f.chkrepeat -side left -padx 5 -pady 5
foreach b {bplay bpause bstop} {$f.$b configure -state disabled}
pack .ma -side top -padx 2 -pady 2 -fill x
pack .fa -side top -padx 2 -pady 2

set m [menu .m]
set mfile [menu $m.mfile]
$mfile add command -label "開く..." -command openSoundFile
$mfile add separator
$mfile add command -label "終了" -command exit
$m add cascade -menu $mfile -label "ファイル"
. configure -menu $m

dnd bindtarget .ma text/uri-list <Drop> {dropped %D}

proc openSoundFile {{filename {}}} {
  global currentSound paused initialDir
  if {"$filename" == ""} {
    set filename [tk_getOpenFile -initialdir $initialDir]
    if {"$filename" == ""} return
  }
  set initialDir [file dirname $filename]

  .fa configure -cursor watch; update
  .ma configure -cursor watch; update
  .ma configure -text [file tail $filename]
  set currentSound [snack::sound s]
  $currentSound read $filename
  .fa configure -cursor {}; update
  .ma configure -cursor {}; update

  set paused 0
  foreach b {bplay bpause bstop} {.fa.$b configure -state normal}
}

proc dropped filenames {
  set filename [lindex $filenames 0]
  after 100 openSoundFile \"$filename\"
}

proc playSound {} {
  global currentSound
  $currentSound play -command endSoundCallback
}

proc stopSound {} {
  global currentSound
  $currentSound stop
}

proc pauseSound {} {
  global currentSound paused
  if $paused {
    set paused 0; .fa.bpause configure -text "一時停止"
  } else {
    set paused 1; .fa.bpause configure -text "再開"
  }
  $currentSound pause
}

proc endSoundCallback {} {
  global repeat
  if $repeat { after 100 playSound }
}
# end.

スクリーンショット

ボリュームのコントロール(Windows)
 Windowsでは、次のようにして、現在のボリュームレベルを取得したり、 変更したりできます。

tk_messageBox -message [snack::audio play_gain]
snack::audio play_gain 30

play_gainの値は0〜100の範囲で設定できます。 音量の単位というのはたまに数字の小さいほうが大きいことがあるので超クセ者ですが、 ここでは0が最小、100が最大です。
 ちなみに、WindowsXP上でこのコマンドで変更できるのは、 ステータスバーの右下に常駐する「音量」→「ボリューム コントロールを開く」 で出てくる「ボリューム コントロール」パネルの「WAVE」の値です。

拡張レビュー分室 top
(first uploaded 2002/11/16 last updated 2004/08/29, MISUMI URANO)