Audio overlap loop: Difference between revisions
(→{{header|JavaScript}}/{{header|HTML}}: style tweak) |
(sample implementation in Tcl) |
||
Line 12:
}
</script></lang>
=={{header|Tcl}}==
Using the popular [http://www.speech.kth.se/snack/ snack] ([http://wiki.tcl.tk/2647 wiki]) extension for audio support, the following presents a GUI to control echo production.
This uses a couple of interesting Tcl features:
* GUI widgets bound to global variables
* playback runs in a coroutine to provide asynchrony with yield
* <tt>lock_play</tt> provides a "transaction"-style control structure similar to "with" in [Python] or (with-*) in [Lisp]
As a bonus, two playback methods are provided - <tt>run</tt> and <tt>mix</tt>, which exercise different capabilities of snack (playing multiple sounds at once vs programmable filters).
Notice that <tt>run</tt> disabled buttons only until the last echo has started, while <tt>mix</tt> does so until the entire playback (cropped to sound level 0) is completed.
Either of these may be desirable in different circumstances, so both are left as an example.
<lang Tcl>package require Tk
package require snack
# variables bound to GUI:
set filename "sample.wav"
set nreps 5
set delay 200
set decay 0.9
# initial snack objects:
snack::sound wav -load sample.wav
snack::sound mixed ;# used by [run]
snack::sound out ;# used by [mix]
snack::sound hush -rate [wav cget -rate] -channels [wav cget -channels]
hush length [wav length]
proc make_gui {} {
grid [label .l0 -text "Filename:"] [button .b0 -textvariable ::filename -command choose_file] -sticky nsew
grid [label .l1 -text "Repetitions"] [entry .e1 -textvariable ::nreps] -sticky nsew
grid [label .l2 -text "Pause"] [entry .e2 -textvariable ::delay] -sticky nsew
grid [label .l3 -text "Decay"] [entry .e3 -textvariable ::decay] -sticky nsew
grid [frame .b] - ;# "-" for colspan
grid [
button .b.run -text "Play" -command {coroutine runner run}
] [
button .b.mix -text "Premix" -command {coroutine runner mix}
] [
button .b.stop -text "Stop" -command stop -state disabled
] [
button .b.exit -text "Exit" -command exit
] -sticky nsew
}
# snack wraps tk_getOpenFile with suitable options to load supported audio files
proc choose_file {} {
global filename
set fn [snack::getOpenFile -initialfile $filename]
if {$fn eq ""} return
wav read [set filename $fn]
}
# disable play and enable stop for the duration of $script
proc lock_play {script} {
.b.run configure -state disabled
.b.mix configure -state disabled
.b.stop configure -state normal
try {
uplevel 1 $script
} finally {
.b.run configure -state normal
.b.mix configure -state normal
.b.stop configure -state disabled
}
}
# play by starting each echo as a distinct sound
proc run {} {
global nreps delay decay
lock_play {
mixed copy wav
mixed play
for {set i 1} {$i < $nreps} {incr i} {
yieldto after $delay [list catch [info coroutine]] ;# delay without blocking the event loop
;# [catch] in case the coroutine has been killed
mixed mix hush -prescaling $decay ;# scale and mix with silence to fade
mixed play
}
}
}
# play using snack::filter to create the echo
proc mix {} {
global nreps delay decay
lock_play {
out copy wav
set args {} ;# for snack::filter echo
for {set i 1} {$i < $nreps} {incr i} {
lappend args [expr {$delay * $i}] [expr {$decay ** $i}]
}
set filter [snack::filter echo 1 1 {*}$args]
out filter $filter
$filter destroy
yieldto out play -command [info coroutine] ;# return to this proc only when playback completed
}
}
# stop playback
proc stop {} {
lock_play {
foreach s {wav mixed out} {
$s stop ;# stop all sounds that may be playing
catch {rename runner {}} ;# kill the coroutine if it exists
}
}
}
make_gui
</lang>
{{omit from|Bc}}
|
Revision as of 02:13, 20 June 2015
Audio Overlap Loop is a program that produces an "echo chamber" effect by playing an audio file several times in an overlapping loop. A repetition level determines the number of times that the file is looped. For the purpose of this task, write a program that takes a parameter for the number of repetitions and plays the file loop.wav in an overlapping loop according to the number of repetitions.
Optionally take parameters for delay between repetitions, and decay (fractional volume reduction between consecutive repetitions).
JavaScript/HTML
<lang JavaScript><script> var j = prompt("Enter the sound manipulation level you want", ""); for(i=0; i<j; i++) {
document.write("<bgsound src='loop.wav'>")
} </script></lang>
Tcl
Using the popular snack (wiki) extension for audio support, the following presents a GUI to control echo production.
This uses a couple of interesting Tcl features:
- GUI widgets bound to global variables
- playback runs in a coroutine to provide asynchrony with yield
- lock_play provides a "transaction"-style control structure similar to "with" in [Python] or (with-*) in [Lisp]
As a bonus, two playback methods are provided - run and mix, which exercise different capabilities of snack (playing multiple sounds at once vs programmable filters). Notice that run disabled buttons only until the last echo has started, while mix does so until the entire playback (cropped to sound level 0) is completed. Either of these may be desirable in different circumstances, so both are left as an example.
<lang Tcl>package require Tk package require snack
- variables bound to GUI:
set filename "sample.wav" set nreps 5 set delay 200 set decay 0.9
- initial snack objects:
snack::sound wav -load sample.wav snack::sound mixed ;# used by [run] snack::sound out ;# used by [mix]
snack::sound hush -rate [wav cget -rate] -channels [wav cget -channels] hush length [wav length]
proc make_gui {} {
grid [label .l0 -text "Filename:"] [button .b0 -textvariable ::filename -command choose_file] -sticky nsew grid [label .l1 -text "Repetitions"] [entry .e1 -textvariable ::nreps] -sticky nsew grid [label .l2 -text "Pause"] [entry .e2 -textvariable ::delay] -sticky nsew grid [label .l3 -text "Decay"] [entry .e3 -textvariable ::decay] -sticky nsew grid [frame .b] - ;# "-" for colspan grid [ button .b.run -text "Play" -command {coroutine runner run} ] [ button .b.mix -text "Premix" -command {coroutine runner mix} ] [ button .b.stop -text "Stop" -command stop -state disabled ] [ button .b.exit -text "Exit" -command exit ] -sticky nsew
}
- snack wraps tk_getOpenFile with suitable options to load supported audio files
proc choose_file {} {
global filename set fn [snack::getOpenFile -initialfile $filename] if {$fn eq ""} return wav read [set filename $fn]
}
- disable play and enable stop for the duration of $script
proc lock_play {script} {
.b.run configure -state disabled .b.mix configure -state disabled .b.stop configure -state normal try { uplevel 1 $script } finally { .b.run configure -state normal .b.mix configure -state normal .b.stop configure -state disabled }
}
- play by starting each echo as a distinct sound
proc run {} {
global nreps delay decay lock_play { mixed copy wav mixed play for {set i 1} {$i < $nreps} {incr i} { yieldto after $delay [list catch [info coroutine]] ;# delay without blocking the event loop ;# [catch] in case the coroutine has been killed mixed mix hush -prescaling $decay ;# scale and mix with silence to fade mixed play } }
}
- play using snack::filter to create the echo
proc mix {} {
global nreps delay decay lock_play { out copy wav set args {} ;# for snack::filter echo for {set i 1} {$i < $nreps} {incr i} { lappend args [expr {$delay * $i}] [expr {$decay ** $i}] } set filter [snack::filter echo 1 1 {*}$args] out filter $filter $filter destroy yieldto out play -command [info coroutine] ;# return to this proc only when playback completed }
}
- stop playback
proc stop {} {
lock_play { foreach s {wav mixed out} { $s stop ;# stop all sounds that may be playing catch {rename runner {}} ;# kill the coroutine if it exists } }
}
make_gui </lang>