|
Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
#!/usr/bin/wish
# (find-es "tcl" "snack")
# (find-es "tcl" "piano.tcl")
# Copied from a Tcler's Wiki page by Richard Suchenwirth, with no changes.
# "A toy piano": http://wiki.tcl.tk/3948
# (find-bgprocess "~/TCL/piano.tcl")
# Bug:
# Could not gain access to /dev/sound/dsp for writing.
# Could not gain access to /dev/sound/dsp for writing.
# while executing
# "$::snd play -filter $::filter"
# invoked from within
# "if $freq {
# $c move $id 1 1
# $::filter configure $freq
# $::snd play -filter $::filter
# $::filter2 configure [expr {$freq/2.}] ..."
# (procedure "play" line 2)
# invoked from within
# "play .c 22 440.0"
# (command bound to event)
package require Tk ;# to make Starkit'ting this easier
package require sound ;# we don't yet use the Tk goodies of snack
set snd [snack::sound -rate 22050]
set snd2 [snack::sound -rate 22050] ;# second sound to add volume
set filter [snack::filter generator 1000 30000 0.7 sine]
set filter2 [snack::filter generator 1000 30000 0.0 sine]
# compute sound frequencies, given a' = 440 Hz
set a 440
# Logarithm to base 2 allows us to proceed linearly in 1/12 steps
set lda [expr {log($a)/log(2)}]
# But this list starts from c'', so we have to add 3/12
set names {c c# d d# e f f# g g# a bb b}
set freqs {}
for {set i 0} {$i<12} {incr i} {
lappend freqs [expr {pow(2, $lda + (3+$i)/12.)}]
}
proc play {c id freq} {
if $freq {
$c move $id 1 1
$::filter configure $freq
$::snd play -filter $::filter
$::filter2 configure [expr {$freq/2.}] ;# one octave lower
$::snd2 play -filter $::filter2
} else {
$c move $id -1 -1
after 20 $::snd stop
after 120 $::snd2 stop
}
}
proc nameof {name factor} {
if {$factor==0.25} {set name [string toupper $name]}
while {$factor>=1} {
append name '
set factor [expr {$factor/2.}]
}
set name
}
set x0 5; set y0 5 ;# top left corner to start
set y1 100 ;# length of white keys
set y05 [expr $y1*.67] ;# length of black keys
set dx 18 ;# width of white keys
set dx2 [expr {$dx/2}] ;# offset of black keys
set c [canvas .c -bg brown -height [expr $y1+5] -width [expr $dx*31]]
$c config -cursor hand2 ;# so we see the single finger that plays
pack $c
wm resizable . 0 0 ;# keep the window fixed-size
foreach factor {0.25 0.5 1 2 4} {
foreach name $names freq $freqs {
set f [expr {$freq * $factor}]
if {[string length $name] == 1} {
set id [$c create rect $x0 $y0 [expr {$x0+$dx}] $y1 -fill white]
incr x0 $dx; incr x0 1
} else {
set x [expr {$x0 - $dx*.35}]
set id [$c create rect $x $y0 [expr {$x + $dx*0.65}] $y05 \
-fill black -tag black]
}
$c bind $id <1> "play $c $id $f" ;# sound on
$c bind $id <ButtonRelease-1> "play $c $id 0" ;# sound off
$c bind $id <Enter> \
[list wm title . "piano: [nameof $name $factor] [format %.1f $f]"]
if {$factor == 4 && $name == "c"} break ;# extra c key at right
}
}
$c raise black ;# otherwise half-hidden by next white key