1#!/bin/sh 2# the next line restarts using wish \ 3exec wish8.4 "$0" "$@" 4 5# An example of how to build a sound application using Snack. 6# Can also be used as a base for specialized applications. 7 8package require -exact snack 2.2 9# Try to load optional file format handlers 10catch { package require snacksphere } 11catch { package require snackogg } 12 13# If they are present add new filetypes to file dialogs 14set extTypes {} 15set loadTypes {} 16set loadKeys {} 17set saveTypes {} 18set saveKeys {} 19if {[info exists snack::snacksphere]} { 20 lappend extTypes {SPHERE .sph} {SPHERE .wav} 21 lappend loadTypes {{SPHERE Files} {.sph}} {{SPHERE Files} {.wav}} 22 lappend loadKeys SPHERE SPHERE 23} 24if {[info exists snack::snackogg]} { 25 lappend extTypes {OGG .ogg} 26 lappend loadTypes {{Ogg Vorbis Files} {.ogg}} 27 lappend loadKeys OGG 28 lappend saveTypes {{Ogg Vorbis Files} {.ogg}} 29 lappend saveKeys OGG 30} 31snack::addExtTypes $extTypes 32snack::addLoadTypes $loadTypes $loadKeys 33snack::addSaveTypes $saveTypes $saveKeys 34 35set v(debug) 0 36snack::sound snd -debug $v(debug) 37set v(rate) 16000 38set v(width) 600 39set v(height) 150 40set v(pps) 10 41set v(start) 0 42set v(end) [snd length] 43set v(pausex) -1 44set v(x0) 0 45set v(fileName) "" 46set v(skip) 0 47set v(rate) 16000 48set v(sfmt) LIN16 49set v(chan) 1 50set v(byteOrder) "" 51 52wm protocol . WM_DELETE_WINDOW exit 53 54pack [set s [scrollbar .scroll -orient horiz -command Scroll]] -fill x 55$s set 0 1 56#bind $s <ButtonRelease-1> Redisplay 57 58pack [set c [canvas .c -width $v(width) -height $v(height) -highlightthi 0]] \ 59 -expand yes -fill both 60$c create waveform 0 0 -sound snd -height $v(height) -width $v(width) \ 61 -tag [list obj wave] -progress snack::progressCallback -trimstart 1 \ 62 -debug $v(debug) 63if {[string match macintosh $::tcl_platform(platform)] || \ 64 [string match Darwin $::tcl_platform(os)]} { 65 $c create rect -1 -1 -1 -1 -tags mark -width 2 -outline red 66} else { 67 $c create rect -1 -1 -1 -1 -tags mark -fill yellow -stipple gray25 \ 68 -width 2 -outline red 69} 70$c create line -1 -1 -1 -1 -fill red -tags playmark 71 72bind $c <ButtonPress-1> { Button1Press %x } 73bind $c <ButtonRelease-1> { Button1Release } 74bind $c <Configure> Reconfigured 75bind $c <Double-Button-1> ClearMark 76 77pack [frame .f] -side bottom -before $c -fill x 78pack [button .f.pl -bitmap snackPlay -command {Play 0}] -side left 79pack [button .f.pa -bitmap snackPause -command Pause] -side left 80pack [button .f.st -bitmap snackStop -command Stop] -side left 81snack::createIcons 82pack [button .f.op -image snackOpen -command LoadSound] -side left 83pack [button .f.zi -image snackZoomIn -command ZoomIn] -side left 84pack [button .f.zo -image snackZoomOut -command ZoomOut] -side left 85pack [radiobutton .f.rs -text Spectrogram -command DrawSpectrogram -val 1] -side left 86pack [radiobutton .f.rw -text Waveform -command DrawWaveform -val ""] -side left 87pack [label .f.l -textvar v(time)] -side left 88 89proc ZoomIn {} { 90 global v c s 91 92 set co [$c coords mark] 93 set start [expr int($v(start) + double($v(rate)) * [lindex $co 0] / $v(pps))] 94 set end [expr int($v(start) + double($v(rate)) * [lindex $co 2] / $v(pps))] 95 if {$start == $end || [snd length] == 0} return 96 97# Update scrollbar 98 $s set [expr double($start)/[snd length]] [expr double($end)/[snd length]] 99 100 set v(pps) [expr $v(width) / (double($end - $start) / $v(rate))] 101 set v(start) $start 102 set v(end) $end 103 ClearMark 104 Redisplay 105} 106 107proc ZoomOut {} { 108 global v c s 109 110 set n 2.0 111 set delta [expr int($v(rate) * $v(width) / $v(pps))] 112 set start [expr int($v(start)-($n-1)/2*$delta)] 113 set end [expr int($v(start)+$delta+($n-1)/2*$delta)] 114 if {$start < 0} { set start 0 } 115 if {$end > [snd length]} { set end [snd length] } 116 if {$start == $end} return 117 118# Update scrollbar 119 $s set [expr double($start)/[snd length]] [expr double($end)/[snd length]] 120 121 set v(pps) [expr $v(width) / (double($end - $start) / $v(rate))] 122 set v(start) $start 123 set v(end) $end 124 ClearMark 125 Redisplay 126} 127 128proc Scroll args { 129 global v s 130 131 set delta [expr int($v(rate) * $v(width) / $v(pps))] 132 if {[lindex $args 0] == "moveto"} { 133 set v(start) [expr int([snd length] * [lindex $args 1])] 134 } elseif {[lindex $args 0] == "scroll"} { 135 if {[lindex $args 1] > 0} { 136 set v(start) [expr $v(start)+$delta] 137 } else { 138 set v(start) [expr $v(start)-$delta] 139 } 140 } 141 if {$v(start) < 0} { set v(start) 0 } 142 if {[expr $v(start)+$delta] > [snd length]} { 143 set v(start) [expr [snd length]-$delta] 144 } 145 set v(end) [expr $v(start)+$delta] 146 147# Update scrollbar 148 $s set [expr double($v(start))/[snd length]] [expr double($v(end))/[snd length]] 149 ClearMark 150 Redisplay 151} 152 153proc Redisplay {} { 154 global v c 155 156# Display section [$start, $end] of the sound 157 $c itemconf obj -start $v(start) -end $v(end) 158} 159 160proc Button1Press {x} { 161 global c 162 163 set xc [$c canvasx $x] 164 $c raise mark 165 $c coords mark $xc 0 $xc [expr [winfo height $c]-2] 166 bind $c <Motion> { Button1Motion %x } 167} 168 169proc Button1Motion {x} { 170 global c 171 172 set xc [$c canvasx $x] 173 if {$xc < 0} { set xc 0 } 174 if {$xc > [winfo width $c]} { set xc [winfo width $c] } 175 set co [$c coords mark] 176 $c coords mark [lindex $co 0] 0 $xc [expr [winfo height $c]-2] 177 ShowTime 178} 179 180proc Button1Release {} { 181 global c 182 183 bind $c <Motion> {} 184 ShowTime 185} 186 187proc DrawSpectrogram {} { 188 global v c 189 190 $c delete obj 191 set colors {#000 #006 #00B #00F #03F #07F #0BF #0FF #0FB #0F7 \ 192 #0F0 #3F0 #7F0 #BF0 #FF0 #FB0 #F70 #F30 #F00} 193 $c create spectrogram 0 0 -sound snd -height [winfo height $c] \ 194 -width [winfo width $c] -start $v(start) -end $v(end) \ 195 -colormap $colors -tag obj -debug $v(debug) 196 $c lower obj 197} 198 199proc DrawWaveform {} { 200 global v c 201 202 $c delete obj 203 if {$v(fileName) == ""} { 204 $c create waveform 0 0 -sound snd -height [winfo height $c] \ 205 -debug $v(debug) -width [winfo width $c] -tag [list obj wave] 206 } else { 207 snack::deleteInvalidShapeFile [file tail $v(fileName)] 208 $c create waveform 0 0 -sound snd -height [winfo height $c] \ 209 -debug $v(debug) -trimstart 1 \ 210 -width [winfo width $c] -start $v(start) -end $v(end) \ 211 -tag [list obj wave] -progress snack::progressCallback 212 snack::makeShapeFileDeleteable [file tail $v(fileName)] 213 } 214 $c lower obj 215} 216 217proc LoadSound {} { 218 global v c s 219 220 set fileName [snack::getOpenFile] 221 if {$fileName == ""} return 222 $c itemconf wave -sound "" 223 set tmps [snack::sound] 224 set ffmt [$tmps read $fileName -end 1 -guessproperties 1] 225 if {$ffmt == "RAW"} { 226 set v(rate) [$tmps cget -rate] 227 set v(sfmt) [$tmps cget -encoding] 228 set v(chan) [$tmps cget -channels] 229 set v(byteOrder) [$tmps cget -byteorder] 230 if {[InterpretRawDialog] == "cancel"} { 231 $tmps destroy 232 return 233 } 234 } 235 $tmps destroy 236 snd config -file $fileName -skip $v(skip) \ 237 -rate $v(rate) -encoding $v(sfmt) -channels $v(chan) \ 238 -byteorder $v(byteOrder) 239 set v(rate) [snd cget -rate] 240 set v(start) 0 241 set v(end) [snd length] 242 set v(pps) [expr $v(width) / (double($v(end) - $v(start)) / $v(rate))] 243 set v(fileName) $fileName 244# Update scrollbar 245 $s set 0.0 1.0 246 wm title . [file tail $fileName] 247 snack::deleteInvalidShapeFile [file tail $fileName] 248 $c itemconf wave -sound snd -start $v(start) -end $v(end) \ 249 -shapefile [file rootname [file tail $fileName]].shape 250 snack::makeShapeFileDeleteable [file tail $fileName] 251 Redisplay 252 ShowTime 253} 254 255proc InterpretRawDialog {} { 256 global v 257 258 set w .rawDialog 259 toplevel $w -class Dialog 260 frame $w.q 261 pack $w.q -expand 1 -fill both -side top 262 pack [frame $w.q.f1] -side left -anchor nw -padx 3m -pady 2m 263 pack [frame $w.q.f2] -side left -anchor nw -padx 3m -pady 2m 264 pack [frame $w.q.f3] -side left -anchor nw -padx 3m -pady 2m 265 pack [frame $w.q.f4] -side left -anchor nw -padx 3m -pady 2m 266 pack [label $w.q.f1.l -text "Sample Rate"] 267 foreach e [snack::audio rates] { 268 pack [radiobutton $w.q.f1.r$e -text $e -val $e -var ::v(rate)]\ 269 -anchor w 270 } 271 pack [label $w.q.f2.l -text "Sample Encoding"] 272 foreach e [snack::audio encodings] { 273 pack [radiobutton $w.q.f2.r$e -text $e -val $e -var ::v(sfmt)]\ 274 -anchor w 275 } 276 pack [label $w.q.f3.l -text Channels] 277 pack [radiobutton $w.q.f3.r1 -text Mono -val 1 -var ::v(chan)] -anchor w 278 pack [radiobutton $w.q.f3.r2 -text Stereo -val 2 -var ::v(chan)] -anchor w 279 pack [radiobutton $w.q.f3.r4 -text 4 -val 4 -var ::v(chan)] -anchor w 280 pack [entry $w.q.f3.e -textvariable ::v(chan) -width 3] -anchor w 281 pack [label $w.q.f4.l -text "Byte Order"] 282 pack [radiobutton $w.q.f4.ri -text "Little Endian\n(Intel)" \ 283 -value littleEndian -var ::v(byteOrder)] -anchor w 284 pack [radiobutton $w.q.f4.rm -text "Big Endian\n(Motorola)" \ 285 -value bigEndian -var ::v(byteOrder)] -anchor w 286 pack [label $w.q.f4.l2 -text "\nRead Offset (bytes)"] 287 pack [entry $w.q.f4.e -textvar v(skip) -wi 6] 288 snack::makeDialogBox $w -title "Interpret Raw File As" -type okcancel 289} 290 291proc ClearMark {} { 292 global c 293 294 $c coords mark -1 -1 -1 -1 295 ShowTime 296} 297 298proc Reconfigured {} { 299 global v c 300 301 if {$v(end) == $v(start)} return 302 set co [$c coords mark] 303 if {[lindex $co 0] != -1} { 304 set start [expr int($v(start) + double($v(rate))*[lindex $co 0] / $v(pps))] 305 set end [expr int($v(start) + double($v(rate))*[lindex $co 2] / $v(pps))] 306 set x0temp [expr int($v(start) + double($v(rate))*$v(x0) / $v(pps))] 307 } 308 set newHeight [winfo height $c] 309 set newWidth [winfo width $c] 310 $c itemconf obj -height $newHeight -width $newWidth 311 set v(pps) [expr $newWidth / (double($v(end) - $v(start)) / $v(rate))] 312 set v(width) $newWidth 313 set v(height) $newHeight 314 if {[lindex $co 0] != -1} { 315 set left [expr double($start - $v(start))/$v(rate)*$v(pps)] 316 set right [expr double($end - $v(start))/$v(rate)*$v(pps)] 317 set v(x0) [expr double($x0temp - $v(start))/$v(rate)*$v(pps)] 318 $c coords mark $left 0 $right [expr [winfo height $c]-2] 319 } 320} 321 322proc Play x { 323 global v c s 324 325 snd stop 326 set c0 [lindex [$c coords mark] 0] 327 set c2 [lindex [$c coords mark] 2] 328 if {$x == 0} { 329 set x $c0 330 if {$c0 == -1} { 331 set l $v(start) 332 set r $v(end) 333 } elseif {$c0 == $c2} { 334 set l [expr int($v(start) + double($v(rate)) * $c0 / $v(pps))] 335 set r $v(end) 336 } else { 337 set l [expr int($v(start) + double($v(rate)) * $c0 / $v(pps))] 338 set r [expr int($v(start) + double($v(rate)) * $c2 / $v(pps))] 339 } 340 } else { 341 if {$c0 == $c2} { 342 set l [expr int($v(start) + double($v(rate)) * $x / $v(pps))] 343 set r $v(end) 344 } else { 345 set l [expr int($v(start) + double($v(rate)) * $x / $v(pps))] 346 set r [expr int($v(start) + double($v(rate)) * $c2 / $v(pps))] 347 } 348 } 349 snd play -start $l -end $r 350 after 0 PutPlayMarker $x 351} 352 353proc Pause {} { 354 global v 355 356 if [snack::audio active] { 357 set v(pausex) [expr $v(x0) + $v(pps) * [snack::audio elapsedTime]] 358 snd stop 359 } elseif {$v(pausex) != -1} { 360 Play $v(pausex) 361 } 362} 363 364proc Stop {} { 365 global v 366 367 snd stop 368 set v(pausex) -1 369} 370 371proc PutPlayMarker args { 372 global v c 373 374 if ![snack::audio active] { 375 $c coords playmark -1 -1 -1 -1 376 ShowTime 377 return 378 } 379 if {$args != ""} { 380 set v(x0) [lindex $args 0] 381 } 382 set x [expr $v(x0) + $v(pps) * [snack::audio elapsedTime]] 383 set co [$c coords mark] 384 if {[lindex $co 0] != [lindex $co 2] && $x > [lindex $co 2]} { 385 $c coords playmark -1 -1 -1 -1 386 ShowTime 387 return 388 } 389 $c coords playmark $x 0 $x $v(height) 390 after 50 PutPlayMarker 391 set time [expr int($v(start) + double($v(rate)) * $x / $v(pps))] 392 set v(time) "Time: [SampleIndex2Time $time]" 393} 394 395proc ShowTime {} { 396 global v c 397 398 set co [$c coords mark] 399 set start [expr int($v(start) + double($v(rate)) * [lindex $co 0] / $v(pps))] 400 set end [expr int($v(start) + double($v(rate)) * [lindex $co 2] / $v(pps))] 401 if {[lindex $co 0] < 0.0} { 402 set v(time) "Length: [SampleIndex2Time [snd length -unit samples]]" 403 return 404 } 405 set v(t1) [SampleIndex2Time $start] 406 set v(t2) [SampleIndex2Time $end] 407 if {$end == $start} { 408 set v(time) "Time: $v(t1)" 409 return 410 } 411 set v(time) "\[$v(t1)-$v(t2)\]" 412} 413 414proc SampleIndex2Time index { 415 global v 416 417 set sec [expr int($index / $v(rate))] 418 set dec [format "%.2d" [expr int(100*((double($index) / $v(rate))-$sec))]] 419 return [clock format $sec -format "%M:%S.$dec"] 420} 421