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