/snack2.2.10/unix/snack.tcl

#
TCL | 1346 lines | 1030 code | 195 blank | 121 comment | 180 complexity | c59e810618edb7e72a2c69507c5811bb MD5 | raw file
  1. #
  2. # Copyright (C) 1997-99 Kare Sjolander <kare@speech.kth.se>
  3. #
  4. # This file is part of the Snack sound extension for Tcl/Tk.
  5. # The latest version can be found at http://www.speech.kth.se/snack/
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20. #
  21. package provide snack 2.2
  22. # Set playback latency according to the environment variable PLAYLATENCY
  23. if {$::tcl_platform(platform) == "unix"} {
  24. if {[info exists env(PLAYLATENCY)] && $env(PLAYLATENCY) > 0} {
  25. snack::audio playLatency $env(PLAYLATENCY)
  26. }
  27. }
  28. namespace eval snack {
  29. namespace export gainBox get* add* menu* frequencyAxis timeAxis \
  30. createIcons mixerDialog sound audio mixer debug
  31. #
  32. # Gain control dialog
  33. #
  34. proc gainBox flags {
  35. variable gainbox
  36. catch {destroy .snackGainBox}
  37. toplevel .snackGainBox
  38. wm title .snackGainBox {Gain Control Panel}
  39. if {[string match *p* $flags]} {
  40. set gainbox(play) [snack::audio play_gain]
  41. pack [scale .snackGainBox.s -label {Play volume} -orient horiz \
  42. -variable snack::gainbox(play) \
  43. -command {snack::audio play_gain} \
  44. -length 200]
  45. }
  46. if {[snack::mixer inputs] != ""} {
  47. if {[string match *r* $flags]} {
  48. set gainbox(rec) [snack::audio record_gain]
  49. pack [scale .snackGainBox.s2 -label {Record gain} \
  50. -orient horiz \
  51. -variable snack::gainbox(rec) \
  52. -command {snack::audio record_gain} \
  53. -length 200]
  54. }
  55. }
  56. pack [button .snackGainBox.exitB -text Close -command {destroy .snackGainBox}]
  57. }
  58. #
  59. # Snack mixer dialog
  60. #
  61. proc flipScaleValue {scaleVar var args} {
  62. set $var [expr 100-[set $scaleVar]]
  63. }
  64. proc mixerDialog {} {
  65. set wi .snackMixerDialog
  66. catch {destroy $wi}
  67. toplevel $wi
  68. wm title $wi "Mixer"
  69. # pack [frame $wi.f0]
  70. # label $wi.f0.l -text "Mixer device:"
  71. # set outDevList [snack::mixer devices]
  72. # eval tk_optionMenu $wi.f0.om mixerDev $outDevList
  73. # pack $wi.f0.l $wi.f0.om -side left
  74. pack [frame $wi.f] -expand yes -fill both
  75. foreach line [snack::mixer lines] {
  76. pack [frame $wi.f.g$line -bd 1 -relief solid] -side left \
  77. -expand yes -fill both
  78. pack [label $wi.f.g$line.l -text $line]
  79. if {[snack::mixer channels $line] == "Mono"} {
  80. snack::mixer volume $line snack::v(r$line)
  81. } else {
  82. snack::mixer volume $line snack::v(l$line) snack::v(r$line)
  83. if {[info exists tile::version]} {
  84. pack [ttk::scale $wi.f.g$line.e -from 0 -to 100 -show no -orient vertical \
  85. -var snack::v(lI$line) -command [namespace code [list flipScaleValue ::snack::v(lI$line) ::snack::v(l$line)]]] -side left -expand yes -fill y
  86. set snack::v(lI$line) [expr 100-[lindex [snack::mixer volume $line] end]]
  87. $wi.f.g$line.e set $snack::v(lI$line)
  88. } else {
  89. pack [scale $wi.f.g$line.e -from 100 -to 0 -show no -orient vertical \
  90. -var snack::v(l$line)] -side left -expand yes -fill both
  91. }
  92. }
  93. if {[info exists tile::version]} {
  94. pack [ttk::scale $wi.f.g$line.s -from 0 -to 100 -show no -orient vertical \
  95. -var snack::v(rI$line) -command [namespace code [list flipScaleValue ::snack::v(rI$line) ::snack::v(r$line)]]] -expand yes -fill y
  96. set snack::v(rI$line) [expr 100-[lindex [snack::mixer volume $line] end]]
  97. $wi.f.g$line.s set $snack::v(rI$line)
  98. } else {
  99. pack [scale $wi.f.g$line.s -from 100 -to 0 -show no -orient vertical \
  100. -var snack::v(r$line)] -expand yes -fill both
  101. }
  102. }
  103. pack [frame $wi.f.f2] -side left
  104. if {[snack::mixer inputs] != ""} {
  105. pack [label $wi.f.f2.li -text "Input jacks:"]
  106. foreach jack [snack::mixer inputs] {
  107. snack::mixer input $jack [namespace current]::v(in$jack)
  108. pack [checkbutton $wi.f.f2.b$jack -text $jack \
  109. -variable [namespace current]::v(in$jack)] \
  110. -anchor w
  111. }
  112. }
  113. if {[snack::mixer outputs] != ""} {
  114. pack [label $wi.f.f2.lo -text "Output jacks:"]
  115. foreach jack [snack::mixer outputs] {
  116. snack::mixer output $jack [namespace current]::v(out$jack)
  117. pack [checkbutton $wi.f.f2.b$jack -text $jack \
  118. -variable [namespace current]::v(out$jack)] \
  119. -anchor w
  120. }
  121. }
  122. pack [button $wi.b1 -text Close -command "destroy $wi"]
  123. }
  124. #
  125. # Snack filename dialog
  126. #
  127. proc getOpenFile {args} {
  128. upvar #0 __snack_args data
  129. set specs {
  130. {-title "" "" "Open file"}
  131. {-initialdir "" "" "."}
  132. {-initialfile "" "" ""}
  133. {-multiple "" "" 0}
  134. {-format "" "" "none"}
  135. }
  136. tclParseConfigSpec __snack_args $specs "" $args
  137. if {$data(-format) == "none"} {
  138. if {$data(-initialfile) != ""} {
  139. set data(-format) [ext2fmt [file extension $data(-initialfile)]]
  140. } else {
  141. set data(-format) WAV
  142. }
  143. }
  144. if {$data(-format) == ""} {
  145. set data(-format) RAW
  146. }
  147. set data(-format) [string toupper $data(-format)]
  148. if {$data(-initialdir) == ""} {
  149. set data(-initialdir) "."
  150. }
  151. if {[string match Darwin $::tcl_platform(os)]} {
  152. return [tk_getOpenFile -title $data(-title) \
  153. -multiple $data(-multiple) \
  154. -filetypes [loadTypes $data(-format)] \
  155. -defaultextension [fmt2ext $data(-format)] \
  156. -initialdir $data(-initialdir)]
  157. }
  158. # Later Tcl's allow multiple files returned as a list
  159. if {$::tcl_version <= 8.3} {
  160. set res [tk_getOpenFile -title $data(-title) \
  161. -filetypes [loadTypes $data(-format)] \
  162. -defaultextension [fmt2ext $data(-format)] \
  163. -initialdir $data(-initialdir) \
  164. -initialfile $data(-initialfile)]
  165. } else {
  166. set res [tk_getOpenFile -title $data(-title) \
  167. -multiple $data(-multiple) \
  168. -filetypes [loadTypes $data(-format)] \
  169. -defaultextension [fmt2ext $data(-format)] \
  170. -initialdir $data(-initialdir) \
  171. -initialfile $data(-initialfile)]
  172. }
  173. return $res
  174. }
  175. set loadTypes ""
  176. proc addLoadTypes {typelist fmtlist} {
  177. variable loadTypes
  178. variable filebox
  179. set loadTypes $typelist
  180. set i 9 ; # Needs updating when adding new formats
  181. foreach fmt $fmtlist {
  182. set filebox(l$fmt) $i
  183. incr i
  184. }
  185. }
  186. proc loadTypes fmt {
  187. variable loadTypes
  188. variable filebox
  189. if {$::tcl_platform(platform) == "windows"} {
  190. set l [concat {{{MS Wav Files} {.wav}} {{Smp Files} {.smp}} {{Snd Files} {.snd}} {{AU Files} {.au}} {{AIFF Files} {.aif}} {{AIFF Files} {.aiff}} {{Waves Files} {.sd}} {{MP3 Files} {.mp3}} {{CSL Files} {.nsp}}} $loadTypes {{{All Files} * }}]
  191. } else {
  192. set l [concat {{{MS Wav Files} {.wav .WAV}} {{Smp Files} {.smp .SMP}} {{Snd Files} {.snd .SND}} {{AU Files} {.au .AU}} {{AIFF Files} {.aif .AIF}} {{AIFF Files} {.aiff .AIFF}} {{Waves Files} {.sd .SD}} {{CSL Files} {.nsp .NSP}}} $loadTypes {{{All Files} * }}]
  193. }
  194. return [swapListElem $l $filebox(l$fmt)]
  195. }
  196. variable filebox
  197. set filebox(RAW) .raw
  198. set filebox(SMP) .smp
  199. set filebox(AU) .au
  200. set filebox(WAV) .wav
  201. set filebox(SD) .sd
  202. set filebox(SND) .snd
  203. set filebox(AIFF) .aif
  204. set filebox(CSL) .nsp
  205. set filebox(lWAV) 0
  206. set filebox(lSMP) 1
  207. set filebox(lSND) 2
  208. set filebox(lAU) 3
  209. set filebox(lAIFF) 4
  210. # skip 2 because of aif and aiff
  211. set filebox(lSD) 6
  212. # skip 1 because of mp3
  213. set filebox(lCSL) 8
  214. set filebox(lRAW) end
  215. # Do not forget to update indexes
  216. set filebox(sWAV) 0
  217. set filebox(sSMP) 1
  218. set filebox(sSND) 2
  219. set filebox(sAU) 3
  220. set filebox(sAIFF) 4
  221. # skip 2 because of aif and aiff
  222. set filebox(sCSL) 6
  223. set filebox(sRAW) end
  224. proc fmt2ext fmt {
  225. variable filebox
  226. return $filebox($fmt)
  227. }
  228. proc addExtTypes extlist {
  229. variable filebox
  230. foreach pair $extlist {
  231. set filebox([lindex $pair 0]) [lindex $pair 1]
  232. }
  233. }
  234. proc getSaveFile args {
  235. upvar #0 __snack_args data
  236. set specs {
  237. {-title "" "" "Save file"}
  238. {-initialdir "" "" "."}
  239. {-initialfile "" "" ""}
  240. {-format "" "" "none"}
  241. }
  242. tclParseConfigSpec __snack_args $specs "" $args
  243. if {$data(-format) == "none"} {
  244. if {$data(-initialfile) != ""} {
  245. set data(-format) [ext2fmt [file extension $data(-initialfile)]]
  246. } else {
  247. set data(-format) WAV
  248. }
  249. }
  250. if {$data(-format) == ""} {
  251. set data(-format) RAW
  252. }
  253. set data(-format) [string toupper $data(-format)]
  254. if {$data(-initialdir) == ""} {
  255. set data(-initialdir) "."
  256. }
  257. if {[string match macintosh $::tcl_platform(platform)]} {
  258. set tmp [tk_getSaveFile -title $data(-title) \
  259. -initialdir $data(-initialdir) -initialfile $data(-initialfile)]
  260. if {[string compare [file ext $tmp] ""] == 0} {
  261. append tmp [fmt2ext $data(-format)]
  262. }
  263. return $tmp
  264. } else {
  265. return [tk_getSaveFile -title $data(-title) \
  266. -filetypes [saveTypes $data(-format)] \
  267. -defaultextension [fmt2ext $data(-format)] \
  268. -initialdir $data(-initialdir) -initialfile $data(-initialfile)]
  269. }
  270. }
  271. set saveTypes ""
  272. proc addSaveTypes {typelist fmtlist} {
  273. variable saveTypes
  274. variable filebox
  275. set saveTypes $typelist
  276. set j 7 ; # Needs updating when adding new formats
  277. foreach fmt $fmtlist {
  278. set filebox(s$fmt) $j
  279. incr j
  280. }
  281. }
  282. proc saveTypes fmt {
  283. variable saveTypes
  284. variable filebox
  285. if {[info exists filebox(s$fmt)] == 0} {
  286. set fmt RAW
  287. }
  288. if {$::tcl_platform(platform) == "windows"} {
  289. set l [concat {{{MS Wav Files} {.wav}} {{Smp Files} {.smp}} {{Snd Files} {.snd}} {{AU Files} {.au}} {{AIFF Files} {.aif}} {{AIFF Files} {.aiff}} {{CSL Files} {.nsp}}} $saveTypes {{{All Files} * }}]
  290. } else {
  291. set l [concat {{{MS Wav Files} {.wav .WAV}} {{Smp Files} {.smp .SMP}} {{Snd Files} {.snd .SND}} {{AU Files} {.au .AU}} {{AIFF Files} {.aif .AIF}} {{AIFF Files} {.aiff .AIFF}} {{CSL Files} {.nsp .NSP}}} $saveTypes {{{All Files} * }}]
  292. }
  293. return [swapListElem $l $filebox(s$fmt)]
  294. }
  295. proc swapListElem {l n} {
  296. set tmp [lindex $l $n]
  297. set l [lreplace $l $n $n]
  298. return [linsert $l 0 $tmp]
  299. }
  300. set filebox(.wav) WAV
  301. set filebox(.smp) SMP
  302. set filebox(.au) AU
  303. set filebox(.raw) RAW
  304. set filebox(.snd) SND
  305. set filebox(.sd) SD
  306. set filebox(.aif) AIFF
  307. set filebox(.aiff) AIFF
  308. set filebox(.nsp) CSL
  309. set filebox() WAV
  310. proc ext2fmt ext {
  311. variable filebox
  312. return $filebox($ext)
  313. }
  314. #
  315. # Menus
  316. #
  317. proc menuInit { {m .menubar} } {
  318. variable menu
  319. menu $m
  320. [winfo parent $m] configure -menu $m
  321. set menu(menubar) $m
  322. set menu(uid) 0
  323. }
  324. proc menuPane {label {u 0} {postcommand ""}} {
  325. variable menu
  326. if [info exists menu(menu,$label)] {
  327. error "Menu $label already defined"
  328. }
  329. if {$label == "Help"} {
  330. set name $menu(menubar).help
  331. } else {
  332. set name $menu(menubar).mb$menu(uid)
  333. }
  334. set m [menu $name -tearoff 1 -postcommand $postcommand]
  335. $menu(menubar) add cascade -label $label -menu $name -underline $u
  336. incr menu(uid)
  337. set menu(menu,$label) $m
  338. return $m
  339. }
  340. proc menuDelete {menuName label} {
  341. variable menu
  342. set m [menuGet $menuName]
  343. if [catch {$m index $label} index] {
  344. error "$label not in menu $menuName"
  345. }
  346. [menuGet $menuName] delete $index
  347. }
  348. proc menuDeleteByIndex {menuName index} {
  349. [menuGet $menuName] delete $index
  350. }
  351. proc menuGet menuName {
  352. variable menu
  353. if [catch {set menu(menu,$menuName)} m] {
  354. return -code error "No such menu: $menuName"
  355. }
  356. return $m
  357. }
  358. proc menuCommand {menuName label command} {
  359. [menuGet $menuName] add command -label $label -command $command
  360. }
  361. proc menuCheck {menuName label var {command {}} } {
  362. variable menu
  363. [menuGet $menuName] add check -label $label -command $command \
  364. -variable $var
  365. }
  366. proc menuRadio {menuName label var {val {}} {command {}} } {
  367. variable menu
  368. if {[string length $val] == 0} {
  369. set val $label
  370. }
  371. [menuGet $menuName] add radio -label $label -command $command \
  372. -value $val -variable $var
  373. }
  374. proc menuSeparator menuName {
  375. variable menu
  376. [menuGet $menuName] add separator
  377. }
  378. proc menuCascade {menuName label} {
  379. variable menu
  380. set m [menuGet $menuName]
  381. if [info exists menu(menu,$label)] {
  382. error "Menu $label already defined"
  383. }
  384. set sub $m.sub$menu(uid)
  385. incr menu(uid)
  386. menu $sub -tearoff 0
  387. $m add cascade -label $label -menu $sub
  388. set menu(menu,$label) $sub
  389. return $sub
  390. }
  391. proc menuBind {what char menuName label} {
  392. variable menu
  393. set m [menuGet $menuName]
  394. if [catch {$m index $label} index] {
  395. error "$label not in menu $menuName"
  396. }
  397. set command [$m entrycget $index -command]
  398. if {$::tcl_platform(platform) == "unix"} {
  399. bind $what <Alt-$char> $command
  400. $m entryconfigure $index -accelerator Alt-$char
  401. } else {
  402. bind $what <Control-$char> $command
  403. set char [string toupper $char]
  404. $m entryconfigure $index -accelerator Ctrl-$char
  405. }
  406. }
  407. proc menuEntryOff {menuName label} {
  408. variable menu
  409. set m [menuGet $menuName]
  410. if [catch {$m index $label} index] {
  411. error "$label not in menu $menuName"
  412. }
  413. $m entryconfigure $index -state disabled
  414. }
  415. proc menuEntryOn {menuName label} {
  416. variable menu
  417. set m [menuGet $menuName]
  418. if [catch {$m index $label} index] {
  419. error "$label not in menu $menuName"
  420. }
  421. $m entryconfigure $index -state normal
  422. }
  423. #
  424. # Vertical frequency axis
  425. #
  426. proc frequencyAxis {canvas x y width height args} {
  427. array set a [list \
  428. -tags snack_y_axis \
  429. -font {Helvetica 8} \
  430. -topfr 8000 \
  431. -fill black \
  432. -draw0 0
  433. ]
  434. if {[string match unix $::tcl_platform(platform)] } {
  435. set a(-font) {Helvetica 10}
  436. }
  437. array set a $args
  438. if {$height <= 0} return
  439. set ticklist [list 10 20 50 100 200 500 1000 2000 5000 \
  440. 10000 20000 50000 100000 200000 500000 1000000]
  441. set npt 10
  442. set dy [expr {double($height * $npt) / $a(-topfr)}]
  443. while {$dy < [font metrics $a(-font) -linespace]} {
  444. foreach elem $ticklist {
  445. if {$elem <= $npt} {
  446. continue
  447. }
  448. set npt $elem
  449. break
  450. }
  451. set dy [expr {double($height * $npt) / $a(-topfr)}]
  452. }
  453. if {$npt < 1000} {
  454. set hztext Hz
  455. } else {
  456. set hztext kHz
  457. }
  458. if $a(-draw0) {
  459. set i0 0
  460. set j0 0
  461. } else {
  462. set i0 $dy
  463. set j0 1
  464. }
  465. for {set i $i0; set j $j0} {$i < $height} {set i [expr {$i+$dy}]; incr j} {
  466. set yc [expr {$height + $y - $i}]
  467. if {$npt < 1000} {
  468. set t [expr {$j * $npt}]
  469. } else {
  470. set t [expr {$j * $npt / 1000}]
  471. }
  472. if {$yc > [expr {8 + $y}]} {
  473. if {[expr {$yc - [font metrics $a(-font) -ascent]}] > \
  474. [expr {$y + [font metrics $a(-font) -linespace]}] ||
  475. [font measure $a(-font) $hztext] < \
  476. [expr {$width - 8 - [font measure $a(-font) $t]}]} {
  477. $canvas create text [expr {$x +$width - 8}] [expr {$yc-2}]\
  478. -text $t -fill $a(-fill)\
  479. -font $a(-font) -anchor e -tags $a(-tags)
  480. }
  481. $canvas create line [expr {$x + $width - 5}] $yc \
  482. [expr {$x + $width}]\
  483. $yc -tags $a(-tags) -fill $a(-fill)
  484. }
  485. }
  486. $canvas create text [expr {$x + 2}] [expr {$y + 1}] -text $hztext \
  487. -font $a(-font) -anchor nw -tags $a(-tags) -fill $a(-fill)
  488. return $npt
  489. }
  490. #
  491. # Horizontal time axis
  492. #
  493. proc timeAxis {canvas ox oy width height pps args} {
  494. array set a [list \
  495. -tags snack_t_axis \
  496. -font {Helvetica 8} \
  497. -starttime 0.0 \
  498. -fill black \
  499. -format time \
  500. -draw0 0 \
  501. -drawvisible 0
  502. ]
  503. if {[string match unix $::tcl_platform(platform)] } {
  504. set a(-font) {Helvetica 10}
  505. }
  506. array set a $args
  507. if {$pps <= 0.004} return
  508. switch -- $a(-format) {
  509. time -
  510. seconds {
  511. set deltalist [list .0001 .0002 .0005 .001 .002 .005 \
  512. .01 .02 .05 .1 .2 .5 1 2 5 \
  513. 10 20 30 60 120 240 360 600 900 1800 3600 7200 14400]
  514. }
  515. "PAL frames" {
  516. set deltalist [list .04 .08 .4 .8 2 4 \
  517. 10 20 50 100 200 500 1000 2000 5000 10000 20000]
  518. }
  519. "NTSC frames" {
  520. set deltalist [list .03333333333334 .0666666666667 \
  521. .3333333333334 .666666666667 1 2 4 \
  522. 10 20 50 100 200 500 1000 2000 5000 10000 20000]
  523. }
  524. "10ms frames" {
  525. set deltalist [list .01 .02 .05 .1 .2 .5 1 2 5 \
  526. 10 20 50 100 200 500 1000 2000 5000 10000 20000]
  527. }
  528. }
  529. set majTickH [expr {$height - [font metrics $a(-font) -linespace]}]
  530. set minTickH [expr {$majTickH / 2}]
  531. # Create a typical time label
  532. set maxtime [expr {double($width) / $pps + $a(-starttime)}]
  533. if {$maxtime < 60} {
  534. set wtime 00
  535. } elseif {$maxtime < 3600} {
  536. set wtime 00:00
  537. } else {
  538. set wtime 00:00:00
  539. }
  540. if {$pps > 50} {
  541. append wtime .0
  542. } elseif {$pps > 500} {
  543. append wtime .00
  544. } elseif {$pps > 5000} {
  545. append wtime .000
  546. } elseif {$pps > 50000} {
  547. append wtime .0000
  548. }
  549. # Compute the distance in pixels (and time) between tick marks
  550. set dx [expr {10+[font measure $a(-font) $wtime]}]
  551. set dt [expr {double($dx) / $pps}]
  552. foreach elem $deltalist {
  553. if {$elem <= $dt} {
  554. continue
  555. }
  556. set dt $elem
  557. break
  558. }
  559. set dx [expr {$pps * $dt}]
  560. if {$dt < 0.00099} {
  561. set ndec 4
  562. } elseif {$dt < 0.0099} {
  563. set ndec 3
  564. } elseif {$dt < 0.099} {
  565. set ndec 2
  566. } else {
  567. set ndec 1
  568. }
  569. if {$a(-starttime) > 0.0} {
  570. set ft [expr {(int($a(-starttime) / $dt) + 1) * $dt}]
  571. set fx [expr {$pps * ($ft - $a(-starttime))}]
  572. } else {
  573. set ft 0
  574. set fx 0.0
  575. }
  576. set lx [expr {($ox + $width) * [lindex [$canvas xview] 0] - 50}]
  577. set rx [expr {($ox + $width) * [lindex [$canvas xview] 1] + 50}]
  578. set jinit 0
  579. if {$a(-drawvisible)} {
  580. set jinit [expr {int($lx/$dx)}]
  581. set fx [expr {$fx + $jinit * $dx}]
  582. }
  583. for {set x $fx;set j $jinit} {$x < $width} \
  584. {set x [expr {$x+$dx}];incr j} {
  585. if {$a(-drawvisible) && $x < $lx} continue
  586. if {$a(-drawvisible) && $x > $rx} break
  587. switch -- $a(-format) {
  588. time {
  589. set t [expr {$j * $dt + $ft}]
  590. if {$maxtime < 60} {
  591. set tmp [expr {int($t)}]
  592. } elseif {$maxtime < 3600} {
  593. set tmp x[clock format [expr {int($t)}] -format "%M:%S" -gmt 1]
  594. regsub x0 $tmp "" tmp
  595. regsub x $tmp "" tmp
  596. } else {
  597. set tmp [clock format [expr {int($t)}] -format "%H:%M:%S" -gmt 1]
  598. }
  599. if {$dt < 1.0} {
  600. set t $tmp[string trimleft [format "%.${ndec}f" \
  601. [expr {($t-int($t))}]] 0]
  602. } else {
  603. set t $tmp
  604. }
  605. }
  606. "PAL frames" {
  607. set t [expr {int($j * $dt * 25.0 + $ft)}]
  608. }
  609. "NTSC frames" {
  610. set t [expr {int($j * $dt * 30.0 + $ft)}]
  611. }
  612. "10ms frames" {
  613. set t [expr {int($j * $dt * 100.0 + $ft)}]
  614. }
  615. seconds {
  616. set t [expr {double($j * $dt * 1.0 + $ft)}]
  617. }
  618. }
  619. if {$a(-draw0) == 1 || $j > 0 || $a(-starttime) > 0.0} {
  620. $canvas create text [expr {$ox+$x}] [expr {$oy+$height}] \
  621. -text $t -font $a(-font) -anchor s -tags $a(-tags) \
  622. -fill $a(-fill)
  623. }
  624. $canvas create line [expr {$ox+$x}] $oy [expr {$ox+$x}] \
  625. [expr {$oy+$majTickH}] -tags $a(-tags) -fill $a(-fill)
  626. if {[string match *5 $dt] || [string match 5* $dt]} {
  627. set nt 5
  628. } else {
  629. set nt 2
  630. }
  631. for {set k 1} {$k < $nt} {incr k} {
  632. set xc [expr {$k * $dx / $nt}]
  633. $canvas create line [expr {$ox+$x+$xc}] $oy \
  634. [expr {$ox+$x+$xc}] [expr {$oy+$minTickH}]\
  635. -tags $a(-tags) -fill $a(-fill)
  636. }
  637. }
  638. }
  639. #
  640. # Snack icons
  641. #
  642. variable icon
  643. set icon(new) R0lGODlhEAAQALMAAAAAAMbGxv///////////////////////////////////////////////////////yH5BAEAAAEALAAAAAAQABAAAAQwMMhJ6wQ4YyuB+OBmeeDnAWNpZhWpmu0bxrKAUu57X7VNy7tOLxjIqYiapIjDbDYjADs=
  644. set icon(open) R0lGODlhEAAQALMAAAAAAISEAMbGxv//AP///////////////////////////////////////////////yH5BAEAAAIALAAAAAAQABAAAAQ4UMhJq6Ug3wpm7xsHZqBFCsBADGTLrbCqllIaxzSKt3wmA4GgUPhZAYfDEQuZ9ByZAVqPF6paLxEAOw==
  645. set icon(save) R0lGODlhEAAQALMAAAAAAISEAMbGxv///////////////////////////////////////////////////yH5BAEAAAIALAAAAAAQABAAAAQ3UMhJqwQ4a30DsJfwiR4oYt1oASWpVuwYm7NLt6y3YQHe/8CfrLfL+HQcGwmZSXWYKOWpmDSBIgA7
  646. set icon(print) R0lGODlhEAAQALMAAAAAAISEhMbGxv//AP///////////////////////////////////////////////yH5BAEAAAIALAAAAAAQABAAAAQ5UMhJqwU450u67wCnAURYkZ9nUuRYbhKalkJoj1pdYxar40ATrxIoxn6WgTLGC4500J6N5Vz1roIIADs=
  647. # set icon(open) R0lGODlhFAATAOMAAAAAAFeEAKj/AYQAV5o2AP8BqP9bAQBXhC8AhJmZmWZmZszMzAGo/1sB/////9zc3CH5BAEAAAsALAAAAAAUABMAQARFcMlJq13ANc03uGAoTp+kACWpAUjruum4nAqI3hdOZVtz/zoS6/WKyY7I4wlnPKIqgB7waet1VqHoiliE+riw3PSXlEUAADs=
  648. # set icon(save) R0lGODlhFAATAOMAAAAAAAAAhAAA/wCEAACZmQD/AAD//4QAAISEAJmZmWZmZszMzP8AAP//AP///9zc3CH5BAEAAAsALAAAAAAUABMAQARBcMlJq5VACGDzvkAojiGocZWHUiopflcsL2p32lqu3+lJYrCZcCh0GVeTWi+Y5LGczY0RCtxZkVUXEEvzjbbEWQQAOw==
  649. # set icon(print) R0lGODlhFAATAOMAAAAAAAAAhAAA/wCEAACZmQD/AAD//4QAAISEAJmZmWZmZszMzP8AAP//AP///9zc3CH5BAEAAAsALAAAAAAUABMAQARHcMlJq53A6b2BEIAFjGQZXlTGdZX3vTAInmiNqqtGY3Ev76bgCGQrGo8toS3DdIycNWZTupMITbPUtfQBznyz6sLl84iRlAgAOw==
  650. # set icon(cut) R0lGODlhFAATAOMAAAAAAAAAhAAA/wCEAACZmQD/AAD//4QAAISEAJmZmWZmZszMzP8AAP//AP///9zc3CH5BAEAAAsALAAAAAAUABMAQAQ3cMlJq71LAYUvANPXVVsGjpImfiW6nK87aS8nS+x9gvvt/xgYzLUaEkVAI0r1ao1WMWSn1wNeIgA7
  651. # set icon(copy) R0lGODlhFAATAOMAAAAAAAAAhAAA/wCEAACZmQD/AAD//4QAAISEAJmZmWZmZszMzP8AAP//AP///9zc3CH5BAEAAAsALAAAAAAUABMAQARFcMlJq5XAZSB0FqBwjSTmnF45ASzbbZojqrTJyqgMjDAXwzNSaAiqGY+UVsuYQRGDluap49RcpLjcNJqjaqEXbxdJLkUAADs=
  652. # set icon(paste) R0lGODlhFAATAOMAAAAAAFeEAKj/AYQAV5o2AP8BqP9bAQBXhC8AhJmZmWZmZszMzAGo/1sB/////9zc3CH5BAEAAAsALAAAAAAUABMAQARTcMlJq11A6c01uFXjAGNJNpMCrKvEroqVcSJ5NjgK7tWsUr5PryNyGB04GdHE1PGe0OjrGcR8qkPPCwsk5nLCLu1oFCUnPk2RfHSqXms2cvetJyMAOw==
  653. # set icon(undo) R0lGODlhFAATAOMAAAAAAAAAhAAA/wCEAACZmQD/AAD//4QAAISEAJmZmWZmZszMzP8AAP//AP///9zc3CH5BAEAAAsALAAAAAAUABMAQAQ7cMlJq6UKALmpvmCIaWQJZqXidWJboWr1XSgpszTu7nyv1IBYyCSBgWyWjHAUnE2cnBKyGDxNo72sKwIAOw==
  654. set icon(cut) R0lGODlhEAAQALMAAAAAAAAAhMbGxv///////////////////////////////////////////////////yH5BAEAAAIALAAAAAAQABAAAAQvUMhJqwUTW6pF314GZhjwgeXImSrXTgEQvMIc3ONtS7PV77XNL0isDGs9YZKmigAAOw==
  655. set icon(copy) R0lGODlhEAAQALMAAAAAAAAAhMbGxv///////////////////////////////////////////////////yH5BAEAAAIALAAAAAAQABAAAAQ+UMhJqwA4WwqGH9gmdV8HiKYZrCz3ecG7TikWf3EwvkOM9a0a4MbTkXCgTMeoHPJgG5+yF31SLazsTMTtViIAOw==
  656. set icon(paste) R0lGODlhEAAQALMAAAAAAAAAhISEAISEhMbGxv//AP///////////////////////////////////////yH5BAEAAAQALAAAAAAQABAAAARMkMhJqwUYWJlxKZ3GCYMAgCdQDqLKXmUrGGE2vIRK7usu94GgMNDqDQKGZDI4AiqXhkDOiMxEhQCeAPlUEqm0UDTX4XbHlaFaumlHAAA7
  657. set icon(undo) R0lGODlhEAAQALMAAAAAhMbGxv///////////////////////////////////////////////////////yH5BAEAAAEALAAAAAAQABAAAAQgMMhJq704622BB93kUSAJlhUafJj6qaLJklxc33iuXxEAOw==
  658. set icon(redo) R0lGODlhFAATAKEAAMzMzGZmZgAAAAAAACH5BAEAAAAALAAAAAAUABMAAAI4hI+py+0fhBQhPDCztCzSkzWS4nFJZCLTMqrGxgrJBistmKUHqmo3jvBMdC9Z73MBEZPMpvOpKAAAOw==
  659. set icon(gain) R0lGODlhFAATAOMAAAAAAFpaWjMzZjMAmZlmmapV/729vY+Pj5mZ/+/v78zM/wAAAAAAAAAAAAAAAAAAACH5BAEAAAUALAAAAAAUABMAAARnsMhJqwU4a32T/6AHdF8WjhUAAoa6kqwhtyW8uUlG4Tl2DqoJjzUcIAIeyZAmAiBwyhUNADQCAsHCUoVBKBTERLQ0RRiftLGoPGgDk1qpC+N2qXPM5lscL/lAAj5CIYQ5gShaN4oVEQA7
  660. set icon(zoom) R0lGODlhFAATAMIAAAAAAF9fXwAA/8zM/8zMzP///wAAAAAAACH5BAEAAAQALAAAAAAUABMAAAM/SLrc/jBKGYAFYapaes0U0I0VIIkjaUZo2q1Q68IP5r5UcFtgbL8YTOhS+mgWFcFAeCQEBMre8WlpLqrWrCYBADs=
  661. set icon(zoomIn) R0lGODlhFAATAMIAAMzMzF9fXwAAAP///wAA/8zM/wAAAAAAACH5BAEAAAAALAAAAAAUABMAAANBCLrc/jBKGYQVYao6es2U0FlDJUjimFbocF1u+5JnhKldHAUB7mKom+oTupiImo2AUAAmAQECE/SMWp6LK3arSQAAOw==
  662. set icon(zoomOut) R0lGODlhFAATAMIAAMzMzF9fXwAAAP///wAA/8zM/wAAAAAAACH5BAEAAAAALAAAAAAUABMAAANCCLrc/jBKGYQVYao6es2U0I2VIIkjaUbidQ0r1LrtGaRj/AQ3boEyTA6DCV1KH82iQigUlYAAoQlUSi3QBTbL1SQAADs=
  663. set icon(play) R0lGODlhFQAVAKEAANnZ2QAAAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAAACwAAAAAFQAVAAACJISPqcvtD10IUc1Zz7157+h5Txg2pMicmESCqLt2VEbX9o1XBQA7
  664. set icon(pause) R0lGODlhFQAVAKEAANnZ2QAAAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAAACwAAAAAFQAVAAACLISPqcvtD12Y09DKbrC3aU55HfBlY7mUqKKO6emycGjSa9LSrx1H/g8MCiMFADs=
  665. set icon(stop) R0lGODlhFQAVAKEAANnZ2QAAAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAAACwAAAAAFQAVAAACJISPqcvtD12YtM5mc8C68n4xIPWBZXdqabZarSeOW0TX9o3bBQA7
  666. set icon(record) R0lGODlhFQAVAKEAANnZ2f8AAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAAACwAAAAAFQAVAAACJoSPqcvtDyMINMhZM8zcuq41ICeOVWl6S0p95pNu4BVe9o3n+lIAADs=
  667. proc createIcons {} {
  668. variable icon
  669. image create photo snackOpen -data $icon(open)
  670. image create photo snackSave -data $icon(save)
  671. image create photo snackPrint -data $icon(print)
  672. image create photo snackCut -data $icon(cut)
  673. image create photo snackCopy -data $icon(copy)
  674. image create photo snackPaste -data $icon(paste)
  675. image create photo snackUndo -data $icon(undo)
  676. image create photo snackRedo -data $icon(redo)
  677. image create photo snackGain -data $icon(gain)
  678. image create photo snackZoom -data $icon(zoom)
  679. image create photo snackZoomIn -data $icon(zoomIn)
  680. image create photo snackZoomOut -data $icon(zoomOut)
  681. image create photo snackPlay -data $icon(play)
  682. image create photo snackPause -data $icon(pause)
  683. image create photo snackStop -data $icon(stop)
  684. image create photo snackRecord -data $icon(record)
  685. }
  686. #
  687. # Support routines for shape files
  688. #
  689. proc deleteInvalidShapeFile {fileName} {
  690. if {$fileName == ""} return
  691. if ![file exists $fileName] return
  692. set shapeName ""
  693. if [file exists [file rootname $fileName].shape] {
  694. set shapeName [file rootname $fileName].shape
  695. }
  696. if [file exists [file rootname [file tail $fileName]].shape] {
  697. set shapeName [file rootname [file tail $fileName]].shape
  698. }
  699. if {$shapeName != ""} {
  700. set fileTime [file mtime $fileName]
  701. set shapeTime [file mtime $shapeName]
  702. if {$fileTime > $shapeTime} {
  703. # Delete shape file if older than sound file
  704. file delete -force $shapeName
  705. } else {
  706. set s [snack::sound]
  707. $s config -file $fileName
  708. set soundSize [expr {200 * [$s length -unit seconds] * \
  709. [$s cget -channels]}]
  710. set shapeSize [file size $shapeName]
  711. if {[expr {$soundSize*0.95}] > $shapeSize || \
  712. [expr {$soundSize*1.05}] < $shapeSize} {
  713. # Delete shape file with incorrect size
  714. file delete -force $shapeName
  715. }
  716. $s destroy
  717. }
  718. }
  719. }
  720. proc makeShapeFileDeleteable {fileName} {
  721. if {$::tcl_platform(platform) == "unix"} {
  722. if [file exists [file rootname $fileName].shape] {
  723. set shapeName [file rootname $fileName].shape
  724. catch {file attributes $shapeName -permissions 0777}
  725. }
  726. if [file exists [file rootname [file tail $fileName]].shape] {
  727. set shapeName [file rootname [file tail $fileName]].shape
  728. catch {file attributes $shapeName -permissions 0777}
  729. }
  730. }
  731. }
  732. #
  733. # Snack default progress callback
  734. #
  735. proc progressCallback {message fraction} {
  736. set w .snackProgressDialog
  737. # if {$fraction == 0.0} return
  738. if {$fraction == 1.0} {
  739. # Task is finished close dialog
  740. destroy $w
  741. return
  742. }
  743. if {![winfo exists $w]} {
  744. # Open progress dialog if not currently shown
  745. toplevel $w
  746. pack [label $w.l]
  747. pack [canvas $w.c -width 200 -height 20 -relief sunken \
  748. -borderwidth 2]
  749. $w.c create rect 0 0 0 20 -fill black -tags bar
  750. pack [button $w.b -text Stop -command "destroy $w.b"]
  751. wm title $w "Please wait..."
  752. wm transient $w .
  753. wm withdraw $w
  754. set x [expr {[winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
  755. - [winfo vrootx [winfo parent $w]]}]
  756. set y [expr {[winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
  757. - [winfo vrooty [winfo parent $w]]}]
  758. wm geom $w +$x+$y
  759. wm deiconify $w
  760. update idletasks
  761. } elseif {![winfo exists $w.b]} {
  762. # User hit Stop button, close dialog
  763. destroy $w
  764. return -code error
  765. }
  766. switch -- $message {
  767. "Converting rate" {
  768. set message "Converting sample rate..."
  769. }
  770. "Converting encoding" {
  771. set message "Converting sample encoding format..."
  772. }
  773. "Converting channels" {
  774. set message "Converting number of channels..."
  775. }
  776. "Computing pitch" {
  777. set message "Computing pitch..."
  778. }
  779. "Reading sound" {
  780. set message "Reading sound..."
  781. }
  782. "Writing sound" {
  783. set message "Writing sound..."
  784. }
  785. "Computing waveform" {
  786. set message "Waveform is being precomputed and\
  787. stored on disk..."
  788. }
  789. "Reversing sound" {
  790. set message "Reversing sound..."
  791. }
  792. "Filtering sound" {
  793. set message "Filtering sound..."
  794. }
  795. }
  796. $w.l configure -text $message
  797. $w.c coords bar 0 0 [expr {$fraction * 200}] 20
  798. update
  799. }
  800. #
  801. # Convenience function to create dialog boxes, derived from tk_messageBox
  802. #
  803. proc makeDialogBox {toplevel args} {
  804. variable tkPriv
  805. set w tkPrivMsgBox
  806. upvar #0 $w data
  807. #
  808. # The default value of the title is space (" ") not the empty string
  809. # because for some window managers, a
  810. # wm title .foo ""
  811. # causes the window title to be "foo" instead of the empty string.
  812. #
  813. set specs {
  814. {-default "" "" ""}
  815. {-message "" "" ""}
  816. {-parent "" "" .}
  817. {-title "" "" " "}
  818. {-type "" "" "okcancel"}
  819. }
  820. tclParseConfigSpec $w $specs "" $args
  821. if {![winfo exists $data(-parent)]} {
  822. error "bad window path name \"$data(-parent)\""
  823. }
  824. switch -- $data(-type) {
  825. abortretryignore {
  826. set buttons {
  827. {abort -width 6 -text Abort -under 0}
  828. {retry -width 6 -text Retry -under 0}
  829. {ignore -width 6 -text Ignore -under 0}
  830. }
  831. }
  832. ok {
  833. set buttons {
  834. {ok -width 6 -text OK -under 0}
  835. }
  836. if {![string compare $data(-default) ""]} {
  837. set data(-default) "ok"
  838. }
  839. }
  840. okcancel {
  841. set buttons {
  842. {ok -width 6 -text OK -under 0}
  843. {cancel -width 6 -text Cancel -under 0}
  844. }
  845. }
  846. retrycancel {
  847. set buttons {
  848. {retry -width 6 -text Retry -under 0}
  849. {cancel -width 6 -text Cancel -under 0}
  850. }
  851. }
  852. yesno {
  853. set buttons {
  854. {yes -width 6 -text Yes -under 0}
  855. {no -width 6 -text No -under 0}
  856. }
  857. }
  858. yesnocancel {
  859. set buttons {
  860. {yes -width 6 -text Yes -under 0}
  861. {no -width 6 -text No -under 0}
  862. {cancel -width 6 -text Cancel -under 0}
  863. }
  864. }
  865. default {
  866. error "bad -type value \"$data(-type)\": must be abortretryignore, ok, okcancel, retrycancel, yesno, or yesnocancel"
  867. }
  868. }
  869. if {[string compare $data(-default) ""]} {
  870. set valid 0
  871. foreach btn $buttons {
  872. if {![string compare [lindex $btn 0] $data(-default)]} {
  873. set valid 1
  874. break
  875. }
  876. }
  877. if {!$valid} {
  878. error "invalid default button \"$data(-default)\""
  879. }
  880. }
  881. # 2. Set the dialog to be a child window of $parent
  882. #
  883. #
  884. if {[string compare $data(-parent) .]} {
  885. set w $data(-parent)$toplevel
  886. } else {
  887. set w $toplevel
  888. }
  889. # 3. Create the top-level window and divide it into top
  890. # and bottom parts.
  891. # catch {destroy $w}
  892. # toplevel $w -class Dialog
  893. wm title $w $data(-title)
  894. wm iconname $w Dialog
  895. wm protocol $w WM_DELETE_WINDOW { }
  896. # Message boxes should be transient with respect to their parent so that
  897. # they always stay on top of the parent window. But some window managers
  898. # will simply create the child window as withdrawn if the parent is not
  899. # viewable (because it is withdrawn or iconified). This is not good for
  900. # "grab"bed windows. So only make the message box transient if the parent
  901. # is viewable.
  902. #
  903. if { [winfo viewable [winfo toplevel $data(-parent)]] } {
  904. wm transient $w $data(-parent)
  905. }
  906. if {![string compare $::tcl_platform(platform) "macintosh"]} {
  907. unsupported1 style $w dBoxProc
  908. }
  909. frame $w.bot
  910. pack $w.bot -side bottom -fill both
  911. if {[string compare $::tcl_platform(platform) "macintosh"]} {
  912. $w.bot configure -relief raised -bd 1
  913. }
  914. # 4. Fill the top part with bitmap and message (use the option
  915. # database for -wraplength and -font so that they can be
  916. # overridden by the caller).
  917. option add *Dialog.msg.wrapLength 3i widgetDefault
  918. if {![string compare $::tcl_platform(platform) "macintosh"]} {
  919. option add *Dialog.msg.font system widgetDefault
  920. } else {
  921. option add *Dialog.msg.font {Times 18} widgetDefault
  922. }
  923. # 5. Create a row of buttons at the bottom of the dialog.
  924. set i 0
  925. foreach but $buttons {
  926. set name [lindex $but 0]
  927. set opts [lrange $but 1 end]
  928. if {![llength $opts]} {
  929. # Capitalize the first letter of $name
  930. set capName [string toupper \
  931. [string index $name 0]][string range $name 1 end]
  932. set opts [list -text $capName]
  933. }
  934. eval button [list $w.$name] $opts [list -command \
  935. [list set [namespace current]::tkPriv(button) $name]]
  936. if {![string compare $name $data(-default)]} {
  937. $w.$name configure -default active
  938. }
  939. pack $w.$name -in $w.bot -side left -expand 1 -padx 3m -pady 2m
  940. # create the binding for the key accelerator, based on the underline
  941. #
  942. set underIdx [$w.$name cget -under]
  943. if {$underIdx >= 0} {
  944. set key [string index [$w.$name cget -text] $underIdx]
  945. bind $w <Alt-[string tolower $key]> [list $w.$name invoke]
  946. bind $w <Alt-[string toupper $key]> [list $w.$name invoke]
  947. }
  948. incr i
  949. }
  950. if {[string compare {} $data(-default)]} {
  951. bind $w <FocusIn> {
  952. if {0 == [string compare Button [winfo class %W]]} {
  953. %W configure -default active
  954. }
  955. }
  956. bind $w <FocusOut> {
  957. if {0 == [string compare Button [winfo class %W]]} {
  958. %W configure -default normal
  959. }
  960. }
  961. }
  962. # 6. Create a binding for <Return> on the dialog
  963. bind $w <Return> {
  964. if {0 == [string compare Button [winfo class %W]]} {
  965. if {$::tcl_version <= 8.3} {
  966. tkButtonInvoke %W
  967. } else {
  968. tk::ButtonInvoke %W
  969. }
  970. }
  971. }
  972. # 7. Withdraw the window, then update all the geometry information
  973. # so we know how big it wants to be, then center the window in the
  974. # display and de-iconify it.
  975. wm withdraw $w
  976. update idletasks
  977. set x [expr {[winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
  978. - [winfo vrootx [winfo parent $w]]}]
  979. set y [expr {[winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
  980. - [winfo vrooty [winfo parent $w]]}]
  981. wm geom $w +$x+$y
  982. wm deiconify $w
  983. # 8. Set a grab and claim the focus too.
  984. set oldFocus [focus]
  985. set oldGrab [grab current $w]
  986. if {[string compare $oldGrab ""]} {
  987. set grabStatus [grab status $oldGrab]
  988. }
  989. grab $w
  990. if {[string compare $data(-default) ""]} {
  991. focus $w.$data(-default)
  992. } else {
  993. focus $w
  994. }
  995. # 9. Wait for the user to respond, then restore the focus and
  996. # return the index of the selected button. Restore the focus
  997. # before deleting the window, since otherwise the window manager
  998. # may take the focus away so we can't redirect it. Finally,
  999. # restore any grab that was in effect.
  1000. tkwait variable [namespace current]::tkPriv(button)
  1001. catch {focus $oldFocus}
  1002. destroy $w
  1003. if {[string compare $oldGrab ""]} {
  1004. if {![string compare $grabStatus "global"]} {
  1005. grab -global $oldGrab
  1006. } else {
  1007. grab $oldGrab
  1008. }
  1009. }
  1010. return $tkPriv(button)
  1011. }
  1012. #
  1013. # Snack level meter implemented as minimal mega widget
  1014. #
  1015. proc levelMeter {w args} {
  1016. array set a [list \
  1017. -oncolor red \
  1018. -offcolor grey10 \
  1019. -background black \
  1020. -width 6 \
  1021. -length 80 \
  1022. -level 0.0 \
  1023. -orient horizontal \
  1024. -type log \
  1025. ]
  1026. array set a $args
  1027. # Widget specific storage
  1028. namespace eval [namespace current]::$w {
  1029. variable levelmeter
  1030. }
  1031. upvar [namespace current]::${w}::levelmeter lm
  1032. set lm(level) 0
  1033. set lm(orient) $a(-orient)
  1034. set lm(oncolor) $a(-oncolor)
  1035. set lm(offcolor) $a(-offcolor)
  1036. set lm(bg) $a(-background)
  1037. set lm(type) $a(-type)
  1038. if {[string match horiz* $lm(orient)]} {
  1039. set lm(height) $a(-width)
  1040. set lm(width) $a(-length)
  1041. } else {
  1042. set lm(height) $a(-length)
  1043. set lm(width) $a(-width)
  1044. }
  1045. set lm(maxtime) [clock seconds]
  1046. set lm(maxlevel) 0.0
  1047. proc drawLevelMeter {w} {
  1048. upvar [namespace current]::${w}::levelmeter lm
  1049. set c ${w}_levelMeter
  1050. $c configure -width $lm(width) -height $lm(height)
  1051. $c delete all
  1052. $c create rectangle 0 0 $lm(width) $lm(height) \
  1053. -fill $lm(oncolor) -outline ""
  1054. $c create rectangle 0 0 0 0 -outline "" -fill $lm(offcolor) \
  1055. -tag mask1
  1056. $c create rectangle 0 0 0 0 -outline "" -fill $lm(offcolor) \
  1057. -tag mask2
  1058. $c create rectangle 0 0 [expr $lm(width)-1] [expr $lm(height)-1] \
  1059. -outline $lm(bg)
  1060. if {[string match horiz* $lm(orient)]} {
  1061. $c coords mask1 [expr {$lm(level)*$lm(width)}] 0 \
  1062. $lm(width) $lm(height)
  1063. $c coords mask2 [expr {$lm(level)*$lm(width)}] 0 \
  1064. $lm(width) $lm(height)
  1065. for {set x 5} {$x < $lm(width)} {incr x 5} {
  1066. $c create line $x 0 $x [expr $lm(width)-1] -fill black \
  1067. -width 2
  1068. }
  1069. } else {
  1070. $c coords mask1 0 0 $lm(width) \
  1071. [expr {$lm(height)-$lm(level)*$lm(height)}]
  1072. $c coords mask2 0 0 $lm(width) \
  1073. [expr {$lm(height)-$lm(level)*$lm(height)}]
  1074. for {set y 5} {$y < $lm(height)} {incr y 5} {
  1075. $c create line 0 [expr $lm(height)-$y] \
  1076. [expr $lm(width)-1] [expr $lm(height)-$y] \
  1077. -fill black -width 2
  1078. }
  1079. }
  1080. }
  1081. proc levelMeterHandler {w cmd args} {
  1082. upvar [namespace current]::${w}::levelmeter lm
  1083. if {[string match conf* $cmd]} {
  1084. switch -- [lindex $args 0] {
  1085. -level {
  1086. set arg [lindex $args 1]
  1087. if {$arg < 1} { set arg 1 }
  1088. if {$lm(type)=="linear"} {
  1089. set lm(level) [expr {$arg/32760.0}]
  1090. } else {
  1091. set lm(level) [expr {log($arg)/10.3972}]
  1092. }
  1093. if {[clock seconds] - $lm(maxtime) > 2} {
  1094. set lm(maxtime) [clock seconds]
  1095. set lm(maxlevel) 0.0
  1096. }
  1097. if {$lm(level) > $lm(maxlevel)} {
  1098. set lm(maxlevel) $lm(level)
  1099. }
  1100. if {[string match horiz* $lm(orient)]} {
  1101. set l1 [expr {5*int($lm(level)*$lm(width)/5)}]
  1102. set l2 [expr {5*int($lm(maxlevel)*$lm(width)/5)}]
  1103. ${w}_levelMeter coords mask1 $l2 0 \
  1104. $lm(width) $lm(height)
  1105. ${w}_levelMeter coords mask2 [expr {$l2-5}] 0 \
  1106. $l1 $lm(height)
  1107. } else {
  1108. set l1 [expr {5*int($lm(level)*$lm(height)/5)}]
  1109. set l2 [expr {5*int($lm(maxlevel)*$lm(height)/5)}]
  1110. ${w}_levelMeter coords mask1 0 0 $lm(width) \
  1111. [expr {$lm(height)-$l2}]
  1112. ${w}_levelMeter coords mask2 0 [expr {$lm(height)-$l2+5}] \
  1113. $lm(width) [expr {$lm(height)-$l1}]
  1114. }
  1115. }
  1116. -length {
  1117. if {[string match horiz* $lm(orient)]} {
  1118. set lm(width) [lindex $args 1]
  1119. } else {
  1120. set lm(height) [lindex $args 1]
  1121. }
  1122. drawLevelMeter $w
  1123. }
  1124. -width {
  1125. if {[string match horiz* $lm(orient)]} {
  1126. set lm(height) [lindex $args 1]
  1127. } else {
  1128. set lm(width) [lindex $args 1]
  1129. }
  1130. drawLevelMeter $w
  1131. }
  1132. default {
  1133. error "unknown option \"[lindex $args 0]\""
  1134. }
  1135. }
  1136. } else {
  1137. error "bad option \"$cmd\": must be configure"
  1138. }
  1139. }
  1140. # Create a canvas where the widget is to be rendered
  1141. canvas $w -highlightthickness 0
  1142. # Replave the canvas widget command
  1143. rename $w ${w}_levelMeter
  1144. # Draw level meter
  1145. drawLevelMeter $w
  1146. # Create level meter widget command
  1147. proc ::$w {cmd args} \
  1148. "return \[eval snack::levelMeterHandler $w \$cmd \$args\]"
  1149. return $w
  1150. }
  1151. }