1#!/bin/sh
2# the next line restarts using wish \
3exec wish8.3 "$0" "$@"
4
5package require -exact snack 2.2
6
7# there is no way (?) to find out from Tk if we can display UNICODE IPA
8# but it seems to be standard on windows installations
9if {[string match windows $tcl_platform(platform)]} {set UNICODE_IPA 1}
10
11switch $tcl_platform(platform) {
12 windows {
13  proc milliseconds { } {clock clicks}
14 }
15 unix {
16  proc milliseconds { } {expr {[clock clicks]/1000}}
17 }
18}
19
20
21set vowels(sw) {
22 O: u      300 600 2350 3250
23 O  \u028a 350 700 2600 3200
24 �: o      400 700 2450 3250
25   \u0254 500 850 2550 3250
26 A: \u0251 600 950 2550 3300
27 A  a      750 1250 2500 3350
28 I: i      250 2200 3150 3750
29 I  \u026a 350 2150 2750 3500
30 E: e      350 2250 2850 3550
31 E/� \u025b 500 1900 2550 3350
32 �3 \ue6   650 1700 2500 3450
33 Y: y      250 2050 2700 3300
34 Y  \u028f 300 2000 2400 3250
35 �: \uf8   400 1750 2300 3350
36   \u153  550 1550 2450 3300
37 �3 ""     550 1150 2450 3250
38 U: \u0289 300 1650 2250 2250
39 U  \u0275 450 1050 2300 3300
40}
41
42set vowels(us) {
43 i i 280 2250 2890 {}
44 I \u026a 400 1920 2560 {}
45 E \u025b 550 1770 2490 {}
46 @ \u00e 6690 1660 2490 {}
47 A \u0251 710 1100 2540 {}
48 > \u0254 590 880 2540 {}
49 U \u028a 450 1030 2380 {}
50 u u 310 870 2250 {}
51}
52
53set vowels(lang) us
54
55proc vok4Create {w {wid 200} {hei 200}} {
56 upvar #0 $w a
57 frame $w -width $wid -height $hei
58 pack [canvas $w.c -bg black] -fill both -expand 1
59 pack propagate $w 0
60 set a(xm) 20
61 set a(ym) 20
62 set a(F10) 800
63 set a(F11) 200
64 set a(F20) 2300
65 set a(F21) 500
66
67 $w.c create line 0 0 0 0 -fill white -tags axes -arrow both
68 $w.c create text 0 0 -anchor e -text F2 -fill yellow -tags ylabel
69 $w.c create text 0 0 -anchor n -text F1 -fill yellow -tags xlabel
70 menu $w.m -tearoff 0
71 $w.m add radiobutton -variable vowels(lang) -value sw \
72   -command [list vok4Config $w] -label "Swedish vowels (after Fant)"
73 $w.m add radiobutton -variable vowels(lang) -value us \
74   -command [list vok4Config $w] -label "American vowels (after Ladefoged)"
75 $w.m add radiobutton -variable vowels(lang) -value NIL \
76   -command [list vok4Config $w] -label "Don't display vowels"
77
78 # trailInit $w 10
79
80 bind $w.c <ButtonPress-1> "vok4Move $w %x %y;Play"
81 bind $w.c <B1-Motion> [list vok4Move $w %x %y]
82 bind $w.c <ButtonRelease-1> "Stop"
83 bind $w.c <Configure> [list vok4Config $w %w %h]
84 bind $w.c <ButtonPress-3> [list tk_popup $w.m %X %Y]
85 return $w
86}
87
88proc vok4Config {w {wid -1} {hei -1}} {
89 upvar #0 $w a
90
91 if {$wid==-1} {
92  set wid $a(width)
93  set hei $a(height)
94 } else {
95  set a(width) $wid
96  set a(height) $hei
97 }
98 set a(x0) $a(xm)
99 set a(x1) [expr $wid-$a(xm)]
100 set a(y0) [expr $hei-$a(ym)]
101 set a(y1) $a(ym)
102 $w.c coords axes $a(x0) $a(y1) \
103   $a(x1) $a(y1) $a(x1) $a(y0)
104 $w.c coords ylabel $a(x0) $a(y1)
105 $w.c coords xlabel $a(x1) $a(y0)
106
107 $w.c delete sym
108
109 set lang $::vowels(lang)
110 if [info exists ::vowels($lang)] {
111  foreach {ascii uni f1 f2 f3 f4} $::vowels($lang) {
112   if [info exists ::UNICODE_IPA] {set sym $uni} else {set sym $ascii}
113   set x [expr {$a(x0)+($a(x1)-$a(x0))*($f2-$a(F20))*1.0/($a(F21)-$a(F20))}]
114   set y [expr {$a(y0)+($a(y1)-$a(y0))*($f1-$a(F10))*1.0/($a(F11)-$a(F10))}]
115   $w.c create text $x $y -font "times 16" -anchor c -text $sym -fill gray -tags sym
116  }
117 }
118}
119
120proc vok4Move {w x y} {
121# puts [info level 0]
122 upvar #0 $w a
123
124 set f1 [expr {int($a(F10)+($a(F11)-$a(F10))*($y-$a(y0))*1.0/($a(y1)-$a(y0)))}]
125 set f2 [expr {int($a(F20)+($a(F21)-$a(F20))*($x-$a(x0))*1.0/($a(x1)-$a(x0)))}]
126 set ::v(f1) $f1
127 set ::v(f2) $f2
128 Config
129 set a(curx) $x
130 set a(cury) $y
131 #  trailUpdate $w
132 return ""
133}
134
135proc updatePreview {} {
136 $::v(pGen) configure \
137   $::v(g,freq) $::v(g,ampl) [expr 0.01*$::v(g,shape)] $::v(g,type) 1024
138 $::v(pF1) configure $::v(f1) $::v(b1)
139 $::v(pF2) configure $::v(f2) $::v(b2)
140 $::v(pF3) configure $::v(f3) $::v(b3)
141 $::v(pF4) configure $::v(f4) $::v(b4)
142
143 preview2 copy s
144 preview2 filter $::v(pAll)
145 preview1 copy s
146 preview1 filter $::v(pGen)
147
148 after cancel updatePreview
149 if {$::v(on) && $::v(g,type)=="noise"} {
150  after 100 updatePreview
151 }
152}
153
154proc Config {args} {
155 $::v(Gen) configure \
156   $::v(g,freq) $::v(g,ampl) [expr 0.01*$::v(g,shape)] $::v(g,type) -1
157 $::v(F1) configure $::v(f1) $::v(b1)
158 $::v(F2) configure $::v(f2) $::v(b2)
159 $::v(F3) configure $::v(f3) $::v(b3)
160 $::v(F4) configure $::v(f4) $::v(b4)
161 updatePreview
162}
163
164proc Play {} {
165 set ::v(on) 1
166 s stop
167 s play -filter $::v(All)
168 updatePreview
169 set ::v(tstart) [milliseconds]
170 #  updateTracks
171 .f1.b config -relief sunken
172}
173
174proc Stop {} {
175 s stop
176 set ::v(on) 0
177 .f1.b config -relief raised
178}
179
180proc Load {} {
181 set file [snack::getOpenFile]
182 if {$file != ""} {s read $file}
183}
184
185proc updateTracks {} {
186 set tt 50
187 set now [milliseconds]
188 set then $::v(tstart)
189 set dt [expr 1.0*([milliseconds]-$::v(tstart))]
190 #set ::v(g,freq) [expr 100+100*(1.0*$dt/$tt)*exp(-$dt/$tt)]
191 set ::v(g,freq) [expr {100+2*cos(2*3.1415*$dt/$tt)}]
192
193 Config
194
195 if $::v(on) {
196  after 50 updateTracks
197 }
198}
199
200proc labeledScale {w args} {
201 array set a {-valwidth 4 -labwidth 8}
202 array set a $args
203 catch {set a(-text) $a(-label)}
204
205 frame $w
206 pack [label $w.l -anchor w -width $a(-labwidth)] -side left
207 foreach opt {-text -bg -width -font} {
208  if [info exists a($opt)] {$w.l config $opt $a($opt)}
209 }
210 pack [scale $w.s -showvalue 0 -bd 1 -width 10] -side left -expand 1 -fill x
211 pack [label $w.v -textvariable $a(-variable) -width $a(-valwidth) -anchor w] -side left
212 foreach opt {-bg -font} {
213  if [info exists a($opt)] {$w.v config $opt $a($opt)}
214 }
215 foreach opt {-length -bg -from -to -variable -orient -resolution -command} {
216  if [info exists a($opt)] {$w.s config $opt $a($opt)}
217 }
218 return $w
219}
220
221proc About {} {
222 set w .about
223 catch {destroy $w}
224 toplevel $w
225 wm title $w "About: Formant Synthesis Demo"
226 set text " This application demonstrates formant-based synthesis
227 of vowels in real time, in the spirit of Gunnar Fant's
228 Orator Verbis Electris (OVE-1) synthesizer of 1953.
229
230 Set source and filter parameters at the top. Click and
231 drag in the \"vowel space\" to hear the vowels.
232 Right-click to select target language for vowel symbols.
233
234 Power spectrum of source (red) and output signal (green) are
235 to the right, waveforms are displayed at the bottom.
236
237 The source type \"sampled\" will use a sound file
238 containing a single period of a waveform as voice source.
239
240 Copyright � 2000 Jonas Beskow
241 Centre for Speech Technology
242 KTH, Stockholm"
243
244 label $w.l -text $text -relief groove -bd 2
245 button $w.b -text OK -command [list set about_done 1]
246 pack $w.l -side top -expand 1 -fill both -padx 5 -pady 5
247 pack $w.b -side top -padx 5 -pady 5
248 if [catch {::tk::PlaceWindow $w center}] {
249  wm geometry $w +[winfo rootx .]+[winfo rooty .]
250 }
251 vwait about_done
252 destroy $w
253}
254
255
256wm title . "Formant Synthesizer Demo"
257wm resizable . 0 0
258
259# Menu bar
260
261menu .m
262.m add cascade -label File -menu [menu .m.file -tearoff 0]
263.m add cascade -label Help -menu [menu .m.help -tearoff 0]
264.m.file add command -label "Load source waveform..." -command Load
265.m.file add separator
266.m.file add command -label Exit -command exit
267.m.help add command -label About... -command About
268. configure -menu .m
269
270# Generator GUI
271
272frame .f1 -relief groove -bd 2
273grid .f1 -row 0 -column 0 -sticky news -padx 5 -pady 5
274label .f1.l -text Source -bg red -anchor w
275tk_optionMenu .f1.gt v(g,type) rectangle triangle sine sampled noise
276button .f1.b -bitmap snackPlay -command Play
277button .f1.c -bitmap snackStop -command Stop
278
279labeledScale .f1.gf -label "Freq." -variable v(g,freq) -from 0.0 -to 1000 -resolution 1.0 -orient horiz -command Config
280labeledScale .f1.ga -label "Ampl." -variable v(g,ampl) -from 0.0 -to 6000 -resolution 1.0 -length 160 -orient horiz -command Config
281labeledScale .f1.gs -label "Shape" -variable v(g,shape) -from 0.0 -to 100 -resolution 1.0 -length 160 -orient horiz -command Config
282
283grid .f1.l .f1.gt .f1.b .f1.c -sticky we -padx 5
284grid .f1.gf -columnspan 4 -sticky we
285grid .f1.ga -columnspan 4 -sticky we
286grid .f1.gs -columnspan 4 -sticky we
287grid columnconfigure .f1 0 -weight 1
288grid rowconfigure .f1 4 -weight 1
289# Formant filter GUI
290
291frame .f2 -relief groove -bd 2
292grid .f2 -row 0 -column 1 -sticky news -padx 5 -pady 5
293label .f2.l -text "Formants" -bg green -anchor w
294grid .f2.l -columnspan 5 -sticky we -padx 5 -pady 5
295label .f2.lf -text "Frequency" -anchor w
296label .f2.lfu -text "Hz "
297label .f2.lb -text "Bandwidth" -anchor w
298label .f2.lbu -text "Hz "
299grid .f2.lf -row 1 -column 1 -sticky w
300grid .f2.lfu -row 1 -column 2 -sticky w
301grid .f2.lb -row 1 -column 3 -sticky w
302grid .f2.lbu -row 1 -column 4 -sticky w
303
304for {set i 1} {$i<=4} {incr i} {
305 label .f2.l0$i -text F$i -width 2
306 scale .f2.f$i -variable v(f$i) -from 0 -to 5000 -resolution 1.0 -orient horiz -command Config -showvalue 0 -bd 1 -width 10
307 label .f2.l1$i -textvariable v(f$i) -anchor w -width 4
308 scale .f2.b$i -variable v(b${i}) -from 1.0 -to 500 -resolution 1.0 -orient horiz -command Config -showvalue 0 -bd 1 -width 10 -length 80
309 label .f2.l2$i -textvariable v(b$i) -anchor w -width 3
310 grid .f2.l0$i .f2.f$i .f2.l1$i .f2.b$i .f2.l2$i -sticky news
311}
312grid columnconfigure .f2 1 -weight 1
313
314set vokh 250
315set vokw 275
316
317# Vowel space
318
319vok4Create .voc $vokw $vokh
320grid .voc -row 1 -column 0 -sticky news
321
322# Spectrum section preview
323
324snack::sound preview1
325snack::sound preview2
326
327set secw $vokw
328set sech $vokh
329
330canvas .c2 -bg black -height 100 -width $secw
331grid .c2 -row 1 -column 1  -sticky news
332.c2 create section 0 0 -sound preview1 -fill red -height $sech -topfrequency 4000 -width $secw -analysistype lpc -tags sect -maxvalue 30
333.c2 create section 0 0 -sound preview2 -fill green -height $sech -topfrequency 4000 -width $secw -analysistype lpc -tags sect -maxvalue 30
334
335foreach freq {1 2 3 4} {
336 set x [expr {$freq*$secw/4.0}]
337 .c2 create line $x 0 $x $sech -fill #999999
338 .c2 create text $x 0 -anchor ne -text $freq -fill #999999
339}
340.c2 create text 0 0 -anchor nw -text kHz -fill #999999
341.c2 raise sect
342
343# Waveforms preview
344
345set wavw 550
346set wavh 90
347
348canvas .c1 -bg black -height 100 -width $wavw
349grid .c1 -row 2 -columnspan 2 -sticky news
350.c1 create waveform 0 50 -anchor w -sound preview1 -fill red  -height $wavh -pixelspersecond 16000
351.c1 create waveform 0 50 -anchor w -sound preview2 -fill green  -height $wavh -pixelspersecond 16000
352
353# Default values
354
355set v(f1) 500
356set v(b1) 50
357set v(f2) 1500
358set v(b2) 75
359set v(f3) 2500
360set v(b3) 100
361set v(f4) 3500
362set v(b4) 150
363set v(g,freq) 75
364set v(g,ampl) 2500
365set v(g,shape) 10
366set v(g,type) rectangle
367
368# Create the filters
369set v(F1) [snack::filter formant $v(f1) $v(b1)]
370set v(F2) [snack::filter formant $v(f2) $v(b2)]
371set v(F3) [snack::filter formant $v(f3) $v(b3)]
372set v(F4) [snack::filter formant $v(f4) $v(b4)]
373set v(Gen) [snack::filter generator $v(g,freq)]
374set v(All) [snack::filter compose $v(Gen) $v(F1) $v(F2) $v(F3) $v(F4)]
375
376# Create spearate filters for the preview
377set v(pF1) [snack::filter formant $v(f1) $v(b1)]
378set v(pF2) [snack::filter formant $v(f2) $v(b2)]
379set v(pF3) [snack::filter formant $v(f3) $v(b3)]
380set v(pF4) [snack::filter formant $v(f4) $v(b4)]
381set v(pGen) [snack::filter generator $v(g,freq)]
382set v(pAll) [snack::filter compose $v(pGen) $v(pF1) $v(pF2) $v(pF3) $v(pF4)]
383
384set v(on) 0
385
386snack::sound s
387snack::createIcons
388
389set samples {135 1477 969 -524 -784 314 781 -19 -543 70 696 366 -141 154 694 484 -122 -179 199 290 136 229 429 293 0 48 326 321 44 -15 210 296 137 99 256 254 82 193 625 800 497 234 346 478 354 264 411 516 420 412 628 724 524 389 563 714 557 378 477 608 476 320 450 658 598 395 380 545 628 558 486 484 461 393 383 446 464 413 399 459 520 559 612 668 670 618 569 536 481 390 312 278 255 224 199 176 152 148 158 119 0 -130 -209 -275 -405 -594 -777 -922 -1046 -1187 -1420 -1822 -2267 -2179}
390s length [llength $samples]
391for {set i 0} {$i<[s length]} {incr i} {
392 s sample $i [lindex $samples $i]
393}
394
395trace variable v(g,type) w Config
396