Working to change the bubbles implementation
[TEvoiceconversion.git] / VoiceConversion.praat
blobb8c5455885c0360560d4d72e9f3cf5e9b58f2321
1 #! praat
2
4 form Select audio
5         sentence Recorded_audio ookhetweer.wav
6         real Pitch 70 (= Hz, mean)
7         real Pitch_SD 15 (= % of mean)
8         real Duration 1.3 (= mult. factor)
9         real HNR 5 (= dB SNR)
10         real Bubbles 1 (= fraction)
11         real Bubbles_SNR 10 (= dB SNR)
12         real Jitter 5 (= %)
13         real Shimmer 10 (= %)
14         positive Voicing_floor_(dB) 15 (= below maximum)
15         boolean Help 0
16 endform
18 ########################################################################
19
20 # VoiceConversion.praat
22 # Change the input speech to resemble Tracheoesophageal speech.
23 # Changes the Pitch (F0) and pitch movements, duration. Filtered noise
24 # is added as well as filtered "bubble" sounds.
25 # Increase the Jitter and Shimmer of a speech recording to the
26 # number given. Cannot reduce Jitter or Shimmer.
27 # Note that Jitter and Shimmer are ill-defined in anything but
28 # sustained vowels.
29
30 # Uses the To PointProcess (periodic, cc) to calculate the jitter
31 # and To PointProcess (periodic, peaks): 60, 300, "yes", "yes"
32 # to change the timing of the periods.
33
34 # Periods are moved with Overlap-and-Add
36 # Shimmer is adapted using additive noise over an intensity tier and
37 # adapting each period individually. Periods are determined with the 
38 # To PointProcess (periodic, peaks) pulses.
40 ########################################################################
42 # Copyright (C) 2016-2017 NKI-AVL, R. J. J. H. van Son
43 # R.v.Son@nki.nl
45 # This program is free software: you can redistribute it and/or modify
46 # it under the terms of the GNU General Public License as published by
47 # the Free Software Foundation, either version 3 of the License, or
48 # (at your option) any later version.
50 # This program is distributed in the hope that it will be useful,
51 # but WITHOUT ANY WARRANTY; without even the implied warranty of
52 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
53 # GNU General Public License for more details.
55 # You should have received a copy of the GNU General Public License
56 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
58 # Full license text is available at:
59 # http://www.gnu.org/licenses/gpl-3.0.html
61 ########################################################################
62
63 # Input parameters (<=0 means "do not change"):
65 # Input file    A file name (with full path). If a Sound object is selected, that will be used instead
66 # Pitch         Average pitch of the new speech in Hz [F'(t) = Fnew/Fold * F(t)]
67 # Pitch SD      Standard deviation of the Pitch of the new speech in Hz (compresses pitch movements)
68 #               [SD'(t) = SDnew/SDold * (F(t) - Faverage) + Faverage]
69 # Duration      Factor with which to multiply the duration
70 # HNR           Signal to Noise ratio of new noise added to obtain the HNR given
71 # Bubbles       Fraction of time bubble sounds are the source (0-1, 0 = disable). Use source "TE_source_bubbles.wav"
72 # Bubbles SNR   Signal to Noise ratio of bubble source (in dB)
73 # Jitter        New jitter in %
74 # Shimmer       New Shimmer in %
75 # Voicing floor Lowest level of sound still considered voiced, in dB below the maximum
76
77 # Help          Print this text and exit
78
79 # Output:
80 # The input sound converted according to the specifications
82 # Print debugging information
83 debug = 1
86 # Output:
87 # A Praat Sound object with the transformed speech
89 # Example:
90 # praat VoiceConversion.praat Speech/Example1.wav 70 15 1.3 5 5 15 5 10 15 no
92 # The Help text
94 if help
95         clearinfo
96         printline Help text
97         printline
98         printline Input parameters (<=0 means "do not change"):
99         printline Input file'tab$''tab$'A file name (with full path). If a Sound object is selected, that will be used instead
100         printline Pitch'tab$''tab$''tab$'Average pitch of the new speech in Hz [F'(t) = Fnew/Fold * F(t)]
101         printline Pitch SD'tab$''tab$'Standard deviation of the Pitch of the new speech in Hz (compresses pitch movements)
102         printline 'tab$''tab$''tab$''tab$'[SD'(t) = SDnew/SDold * (F(t) - Faverage) + Faverage]
103         printline Duration'tab$''tab$'Factor with which to multiply the duration
104         printline HNR'tab$''tab$''tab$''tab$'Signal to Noise ratio of new noise added to obtain the HNR given
105         printline Bubbles'tab$''tab$''tab$'Fraction of time bubble sounds are the source (0-1, 0 = disable). Use source "TE_source_bubbles.wav"
106         printline Bubbles SNR'tab$''tab$'Signal to Noise ratio of bubble sounds added (in dB)
107         printline Jitter'tab$''tab$''tab$'New jitter in %
108         printline Shimmer'tab$''tab$''tab$'New Shimmer in %
109         printline Voicing floor'tab$'Lowest level of sound still considered voiced, in dB below the maximum
110         printline
111         printline Help'tab$''tab$''tab$'Print this text and exit
112         printline
113         printline Output:
114         printline The input sound converted according to the specifications
115         exit
116 endif
121 if numberOfSelected("Sound") > 0
122         .recordedSound = selected("Sound")
123 elsif recorded_audio$ <> "" and fileReadable(recorded_audio$) 
124         .recordedSound = Read from file: recorded_audio$
125         Rename: "RecordedSpeech"
126 endif
128 bubblesAudioName$ = "bubbles.wav"
130 te_source_bubbles_name$ = "TE_source_bubbles.wav"
132 .thresshold = -voicing_floor
134 # Scale intensity:
135 select .recordedSound
136 global.setIntensity = Get intensity (dB)
138 call convert_speechOverlapAndAdd  .recordedSound .thresshold jitter shimmer pitch pitch_SD duration hNR bubbles bubbles_SNR
140 # Definitions of functions
142 # Main functions
143 procedure convert_speechOverlapAndAdd .recordedSound .thresshold .jitter .shimmer .pitch .pitch_SD .durationFactor .newHNR .bubble_fraction .bubble_snr
144         call change_pitch_duration .recordedSound .pitch .pitch_SD .durationFactor
145         .newPitchSound = selected("Sound")
146         
147         call extractVoicingParameters .newPitchSound .thresshold
148         .recordedTextGrid = selected("TextGrid")
149         .recordedPulses = selected("PointProcess")
150         .recordedInt = selected("Intensity")
151         
152         select .newPitchSound
153         .recordedPulsesPeaks = To PointProcess (periodic, peaks): 60, 300, "yes", "yes"
155         if .newHNR > 0
156                 call create_additive_noise .newPitchSound .recordedTextGrid
157                 .additiveNoise = selected("Sound")
158         else
159                 .additiveNoise = -1
160         endif
161         
162         #call add_bubbles '.newPitchSound' '.bubble_fraction' '.bubble_snr' '.recordedTextGrid' 'bubblesAudioName$'
163         #.additiveBubbles = selected("Sound")
164         
165         # Change Jitter, use CC to determine jitter and Peaks to change the periods
166         selectObject: .recordedPulsesPeaks
167         .newPointProcess = Copy: "New_Pulses"
168         call set_jitter .jitter .newPointProcess .recordedPulses
169         call test_overlap_add .newPitchSound .recordedPulsesPeaks .recordedTextGrid .newPointProcess .shimmer
170         .newSound = selected("Sound")
171         
172         # Create bubbles from source
173         call add_source_bubbles '.newPitchSound' '.recordedPulses' 0.7 10 'te_source_bubbles_name$'
174         .additiveBubbles = selected("Sound")
175         
176         # Debug tests
177         if debug
178                 # Old numbers
179                 selectObject: .recordedPulses
180                 .old_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
181                 selectObject: .newPointProcess
182                 .newPPjitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
183                 selectObject: .recordedSound
184                 plus .recordedPulses
185                 .old_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
186                 .old_shimmer = Get shimmer (local): 0.0001, 0.03, 2
187                 
188                 
189                 selectObject: .newSound
190                 .pointP = To PointProcess (periodic, cc): 60, 300
191                 .new_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
193                 selectObject: .newSound
194                 plus .pointP
195                 .new_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
196                 .new_shimmer = Get shimmer (local): 0.0001, 0.03, 2
198                 appendInfoLine:  "New Jitter: '.new_jitter:1%' ('.old_jitter:1%' ~> '.newPPjitter:1%')"
199                 appendInfoLine:  "New Shimmer: '.new_shimmer:1%' ('.old_shimmer:1%')"
200                 
201                 selectObject: .old_amplitude, .pointP, .new_amplitude
202                 Remove
203         endif
204         
205         # Add noise to result
206         call add_sounds .newSound .additiveNoise .newHNR
207         .resultNoise = selected("Sound")
208         Rename: "NewSpeech"
209         
210         # Add bubbles to result
211         call add_sounds .resultNoise .additiveBubbles .bubble_snr
212         .result = selected("Sound")
213         Rename: "NewSpeech"
214         
215         # Clean up
216         selectObject: .newPitchSound, .recordedTextGrid, .recordedPulses, .recordedInt, .newPointProcess, .recordedPulsesPeaks, .newSound, .additiveNoise, .additiveBubbles, .resultNoise
217         Remove
218         
219         selectObject: .result
220         Scale intensity: 70
221 endproc
223 procedure change_pitch_duration .sound .pitch .pitchFraction .durationFactor
224         select .sound
225         .duration = Get total duration
226         
227         .manipulation = To Manipulation: 0.01, 70, 300
228         .pitchTier = Extract pitch tier
229         .currentPitch = Get mean (points): 0, 0
230         .pitch_SD = .pitchFraction / 100 * .pitch
231         
232         select .manipulation
233         .durationTier = Extract duration tier
234         
235         # Change duration
236         if .durationFactor > 0
237                 .numPoints = Get number of points
238                 if .numPoints <= 0
239                         Add point: 0, 1
240                 endif
241                 Formula: "self*'.durationFactor'"
242                 select .manipulation
243                 plus .durationTier
244                 Replace duration tier
245         endif
246         
247         if .pitch > 0
248                 select .pitchTier
249                 .factor = (.pitch / .currentPitch)
250                 Multiply frequencies: 0, .duration, .factor
251                 .currentSD = Get standard deviation (points): 0, 0
252                 
253                 if .currentSD > 0
254                         .factor = .pitch_SD / .currentSD
255                         Formula: "'.pitch' + (self - '.pitch') * '.factor'"
256                 endif
258                 select .manipulation
259                 plus .pitchTier
260                 Replace pitch tier
261         endif
262         
263         .newSound = -1
264         if .currentPitch > 0 or .durationFactor > 0
265                 select .manipulation
266                 .newSound = Get resynthesis (overlap-add)
267         else
268                 select .sound 
269                 .newSound = Copy: "New Sound"
270         endif
271         select .manipulation
272         plus .pitchTier
273         plus .durationTier
274         Remove
275         
276         select .newSound
277 endproc
278         
279 procedure extractVoicingParameters .recordedSound .thresshold
280         # The lowest level of voiced sounds
281         select .recordedSound
282         .pointPcc = To PointProcess (periodic, cc): 60, 300
283         Rename: "RecordedPulses"
284         .textGrid = To TextGrid (vuv): 0.02, 0.01
285         Rename: "RecordedVoicing"
286         .numIntervals = Get number of intervals: 1
288         # Correct voicing boundaries
289         select .recordedSound
290         .intensity = To Intensity: 100, 0, "yes"
291         Rename: "RecordedIntensity"
292         .silences = To TextGrid (silences): .thresshold, 0.1, 0.05, "silent", "sounding"
294         # Start boundaries
295         for .i to .numIntervals
296                 select .textGrid
297                 .label$ = Get label of interval: 1, .i
298                 if .label$ = "V"
299                         .start = Get starting point: 1, .i
300                         .end = Get end point: 1, .i
301                         
302                         # Starting point of voiced interval
303                         select .silences
304                         .s = Get interval at time: 1, .start
305                         .sLabel$ = Get label of interval: 1, .s
306                         if .sLabel$ = "silent"
307                                 .sStart = Get starting point: 1, .s
308                                 .sEnd = Get end point: 1, .s
309                                 select .textGrid
310                                 if .sEnd < .end
311                                         Set interval text: 1, .i, "U"
312                                         # Shift boundaries: Insert&Remove
313                                         Insert boundary: 1, .sEnd
314                                         Set interval text: 1, .i+1, "V"
315                                         if .i > 1
316                                                 Set interval text: 1, .i, ""
317                                                 Remove left boundary: 1, .i
318                                         endif
319                                 else
320                                         # Low intensity, unvoiced
321                                         Set interval text: 1, .i, "U"
322                                 endif
323                         endif
324                 endif
325         endfor
326         
327         # End boundaries
328         for .i to .numIntervals
329                 select .textGrid
330                 .label$ = Get label of interval: 1, .i
331                 if .label$ = "V"
332                         .start = Get starting point: 1, .i
333                         .end = Get end point: 1, .i
334                         
335                         # Starting point of voiced interval
336                         select .silences
337                         .s = Get interval at time: 1, .end
338                         .sLabel$ = Get label of interval: 1, .s
339                         if .sLabel$ = "silent"
340                                 .sStart = Get starting point: 1, .s
341                                 .sEnd = Get end point: 1, .s
342                                 select .textGrid
343                                 if .sStart > .start
344                                         Set interval text: 1, .i, "U"
345                                         # Shift boundaries: Insert&Remove
346                                         Insert boundary: 1, .sStart
347                                         Set interval text: 1, .i, "V"
348                                         if .i > 1
349                                                 Set interval text: 1, .i+1, ""
350                                                 Remove right boundary: 1, .i+1
351                                         endif
352                                 else
353                                         # Low intensity, unvoiced
354                                         Set interval text: 1, .i, "U"
355                                 endif
356                         endif
357                 endif
358         endfor
359         
360         select .silences
361         Remove
362         
363         select .textGrid
364         plus .pointPcc
365         plus .intensity
366 endproc
368 procedure create_additive_noise .sound .vuvTextGrid
369         select .sound
370         .duration = Get total duration
371         .sampleFreq = Get sampling frequency
373         .additiveNoiseSound = -1
374                 
375         # Get filter
376         select .sound
377         .downsampled = Resample: 10000, 50
378         .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
379         plus .downsampled
380         .source = Filter (inverse)
381         .sourceInt = To Intensity: 70, 0, "yes"
382         .sourceIntTier = To IntensityTier (peaks)
384         # Create additive noise
385         .noise = Create Sound from formula: "WhiteNoise", 1, 0, .duration, .sampleFreq, "randomGauss(0,0.1)"
386         plus .lpc
387         .filteredNoise = Filter: "no"
388         plus .sourceIntTier
389         .additiveNoiseSoundTMP = Multiply: "yes"
390         call set_VUV_to_zero .additiveNoiseSoundTMP .vuvTextGrid U
391         .additiveNoiseSound = Resample: .sampleFreq, 50
392         
393         selectObject: .noise, .filteredNoise, .additiveNoiseSoundTMP
394         Remove
395         
396         selectObject: .downsampled, .lpc, .source, .sourceInt, .sourceIntTier
397         Remove
398         
399         if .additiveNoiseSound <= 0
400                 .additiveNoiseSound = Create Sound from formula: "AdditiveNoise", 1, 0, .duration, .sampleFreq, "0"
401         endif
402         
403         select .additiveNoiseSound
404 endproc
406 procedure add_sounds .sound1 .sound2 .s1_s2ratioDB
407         .tmp1 = -1
408         .tmp2 = -1
409         if .sound1 > 0
410                 select .sound1
411                 .tmp1 = Copy: "Sound1"
412                 .int1 = Get intensity (dB)
413                 .duration = Get total duration
414                 .sampleFreq = Get sampling frequency
415         endif
416                 
417         if .sound2 > 0
418                 select .sound2
419                 .tmp2 = Copy: "Sound2"
420                 .int2 = Get intensity (dB)
421                 .duration = Get total duration
422                 .sampleFreq = Get sampling frequency
423         endif
424         
425         if .tmp1 <= 0
426                 .tmp1 = Create Sound from formula: "BubblesNoise1", 1, 0, .duration, .sampleFreq, "0"
427         elsif .tmp2 <= 0
428                 .tmp2 = Create Sound from formula: "BubblesNoise2", 1, 0, .duration, .sampleFreq, "0"
429         endif
430         
431         if .int1 - .int2 <> .s1_s2ratioDB
432                 .ratio = .s1_s2ratioDB - (.int1 - .int2)
433                 select .tmp1
434                 Scale intensity: .int1 + .ratio / 2
435                 select .tmp2
436                 Scale intensity: .int2 - .ratio / 2
437         endif
438         selectObject: .tmp1, .tmp2
439         .stereo = Combine to stereo
440         .addedSound = Convert to mono
441         
442         selectObject: .stereo, .tmp1, .tmp2
443         Remove
444         
445         selectObject: .addedSound
446 endproc
449 # Set Jitter to a specified number
451 # Ti = ti - ti-1 (interval i)
452 # Jitter (absolute)  is Sum[ abs(Ti - Ti-1) ] / N-1
453 # Jitter = Jitter (absolute) / mean(Ti)
455 # For a Normal distribution
456 # E(|X|) = sqrt(2/pi) * stdev(X)
458 # E(Ti - Ti-1) = 0
459 # E(Ti^2) = var(Ti) + E(Ti)^2
460 # E(Ti*Ti-1) = cor(Ti, Ti-1) + E(Ti)^2
461 # var(Ti - Ti-1) = E(Ti^2 - 2*Ti*Ti-1 + Ti-1^2)
462 #                = 2*E(Ti^2) - 2*E(Ti*Ti-1)
463 #                = 2*[ var(Ti) * (1 - cor(Ti, Ti-1)) ]
465 # Combine, assuming a Normal distribution:
466 # Jitter = E(|Ti - Ti-1|) / E(Ti)
467 #        = sqrt(2/pi) * stdev(Ti - Ti-1) / mean(Ti)
468 #        = sqrt(2/pi * var(Ti - Ti-1)) / mean(Ti)
469 #        = sqrt[ 4/pi * ( var(Ti) * (1 - cor(Ti, Ti-1)) ) ] / mean(Ti)
471 # Change Ti -> T'i; Jitter -> a*Jitter while keeping mean(Ti) = mean(T'i) constant
472 # ei = (ti + ti-2)/2 - ti-1
473 # Jitter' = a * Jitter
474 #         = a * sqrt[ 4/pi * var(Ti - Ti-1) ] / mean(Ti)
476 # => var(T'i - T'i-1) = a^2 * var(Ti - Ti-1) 
477 #                     = a^2 * E[ (Ti - Ti-1)^2 ]
478 #                     = a^2 * E[ (ti - ti-1 - ti-1 + ti-2)^2 ]
479 #                     = a^2 * 2 * E[ ((ti + ti-2)/2 - ti-1)^2 ]
480 #                     = a^2 * 2 * E[ ei^2 ]
481 #                     = 2 * E[ (a*ei)^2 ]
482 #                     = 2 * var(ei')
484 # Generalizing, var(T'i - T'i-1) = 2*(var(ti-1) + var(ti) + var(ti+1))
485 # To increase Jitter -> Jitter'
486 # 1) Determine var(Ti - Ti-1) = (Jitter * mean(Ti))^2 * pi / 2
487 # 2) Calculate var(T'i - T'i-1) = (Jitter' * mean(T'i))^2 * pi / 2
488 # 3) Determine var to add: 
489 #    add_var(Ti - Ti-1) = var(T'i - T'i-1) - var(Ti - Ti-1)
490 # 4) Var of Noise to add per ti: add_var(ti) = add_var(Ti - Ti-1)/(2*3)
491 # 5) Sd of Noise to add per ti: add_sd(ti) = sqrt(add_var(ti))
493 # .newJitter is in %
494 # Converts .pulses into pulses with new Jitter
495 procedure set_jitter .newJitter .pulses .pulsesCC
496         
497         if .pulses > 0 and .newJitter > 0
498                 .newJitter /= 100
499                 select .pulses
500                 # Use CC to determine real jitter
501                 if .pulsesCC > 0
502                         select .pulsesCC
503                 endif
504                 .current_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
505                 .current_abs_jitter = Get jitter (local, absolute): 0, 0, 0.0001, 0.03, 2
506                 .current_mean_period = Get mean period: 0, 0, 0.0001, 0.03, 2
507                 .current_stdev_period = Get stdev period: 0, 0, 0.0001, 0.03, 2
509                 if .newJitter > .current_jitter
510                         .current_var = .current_abs_jitter**2 * pi/2
511                         .end_var = (.newJitter * .current_mean_period)**2 * pi/2
512                         # The variance to add per boundary (total / (2*3))
513                         .add_var = (.end_var - .current_var) / 6
514                         .stdev_e = sqrt(.add_var)
515                         
516                         # Keep the original pulses just is case the order of the pulses might change
517                         select .pulses
518                         .origPulses = Copy: "Original_Pulses"
519                         .numPoints = Get number of points
520                         
521                         # New jitter
522                         # Change jitter by moving the ti according to 
523                         # t'i = ti - randomGauss(0, stdev(e'))
524                         for .p from 1 to .numPoints
525                                 select .origPulses
526                                 .t = Get time from index: .p
527                                 .new_t = .t - randomGauss(0, .stdev_e)
528                 
529                                 # Remove current point 
530                                 select .pulses
531                                 .r = Get nearest index: .t
532                                 Remove point: .r
533                                 Add point: .new_t
534                         endfor
535                         
536                         select .origPulses
537                         Remove
538                 else
539                         pause New jitter: '.newJitter' must be larger than current jitter '.current_jitter:4'
540                 endif
541                 
542                 # Calculate new jitter
543                 select .pulses
544                 .jitter_new = Get jitter (local): 0, 0, 0.0001, 0.03, 2
545                 .jitter_new *= 100
546                 .current_jitter *= 100
547         endif
548         
549         select .pulses
550 endproc
553 # We cannot use the shimmer of a sentence, so we can only "add" shimmer
555 # .new_shimmer is in %
556 # .sound: Source Sound
557 # .pulses: PointProcess
558 # .voicing: VUV TextGrid
559 # .new_shimmer: New shimmer in %
560 procedure increase_shimmer .sound .pulses .voicing .newShimmer
561         if .newShimmer > 0
562                 .newShimmer /= 100
563                 .shimmer_new = 0
564                 
565                 # Create Amplitude Tier and get current shimmer
566                 select .sound
567                 .duration = Get total duration
568                 plus .pulses
569                 .current_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
570                 .current_shimmer = Get shimmer (local): 0.0001, 0.03, 2
571                 select .current_amplitude
572                 .numPoints = Get number of points
573                 .ampreal = Down to TableOfReal
574                 .sumamp = 0
575                 .n = 0
576                 for .p from 1 to .numPoints
577                         select .ampreal
578                         .tmp = Get value: .p, 2
579                         if .tmp > 0
580                                 .sumamp += .tmp
581                                 .n += 1
582                         endif
583                 endfor
584                 .meanAmp = .sumamp / .n
585                 
586                 # Sd must be multiplied with the amplitude
587                 if .newShimmer > .current_shimmer
588                         .new_var = (.newShimmer**2 - .current_shimmer**2) * .meanAmp**2 * pi / 2
589                 else
590                         .new_var = .newShimmer**2 * .meanAmp**2 * pi / 2
591                 endif
592                 if .new_var > 0 
593                         .new_sd = sqrt(.new_var / 2)
594                 else
595                         .new_sd = 0
596                 endif
597                 
598                 .new_amplitude = Create AmplitudeTier: "New_Amplitude", 0, .duration
599                 for .p from 1 to .numPoints
600                         select .ampreal
601                         .t = Get value: .p, 1
602                         .a = Get value: .p, 2
603                         if .a = undefined
604                                 .a = 0
605                         endif
606                         if .a > 0
607                                 .new_a = .a - randomGauss(0, .new_sd)
608                                 if .new_a < 0 
609                                         .new_a = 0
610                                 endif
611                                 
612                                 # Add new value
613                                 select .new_amplitude
614                                 Add point: .t, .new_a / .a
615                         else
616                                 Add point: .t, .a
617                         endif
618                 endfor
619                 
620                 # Set unvoiced parts to 1
621                 select .new_amplitude
622                 Add point: 0, 1
623         
624                 select .voicing
625                 .numIntervals = Get number of intervals: 1
626                 for .i from 1 to .numIntervals
627                         select .voicing
628                         .t = Get end point: 1, .i
629                         select .new_amplitude
630                         Add point: .t, 1
631                 endfor
632                 
633                 select .ampreal
634                 plus .current_amplitude
635                 Remove
636                 
637                 # Overlay shimmer over sound
638                 select .sound
639                 plus .new_amplitude
640                 .new_sound = Multiply
641                 Rename: "NewSound_Shimmer"
642                 
643                 select .new_sound
644                 plus .pulses
645                 .shimmer_new = Get shimmer (local): 0, 0, 0.0001, 0.02, 1.3, 1.6
646                 .shimmer_new *= 100
647                 .current_shimmer *= 100
648                 .newShimmer *= 100
649         
650                 select .new_amplitude
651                 Remove
652                 
653         else
654                 select .sound
655                 .new_sound = Copy: "NewSound_Shimmer"
656         endif
657         
658         select .new_sound
659 endproc
661 # Make a copy of the source to the target matching the pulses in source and target
662 # Copies fragments around pulses in sourcePulses under the direction of the 
663 # corresponding pulses in targetPulses using the Overlap&Add method (Gaussian window)
665 # Ignores voiceless parts, ie, intervals between pulses > .maxInt
666 # For voices, .maxInt should be ~0.02 (F0 > 50Hz). For other sounds, e.g., bubbles, this
667 # should be increased to fit the whole sound between pulses.
669 # Midpoint between the pulses, periods add up to a factor of ~1.04. 
670 # At the pulses themselves, it adds up to ~1.12 (summed left and right)
672 procedure overlap_add .sourceSound .sourcePulses .targetSound .targetPulses .maxInt
673         # Create empty .targetSound if .targetSound does not exist
674         if .targetSound <= 0
675                 select .sourceSound
676                 .duration = Get total duration
677                 .samplingFrequency = Get sampling frequency
678                 .targetSound = Create Sound from formula: "Target Sound", 1, 0, .duration, .samplingFrequency, "0"
679         endif
680         # Default, just copy the source pulses
681         if .targetPulses <= 0
682                 .targetPulses = .sourcePulses
683         endif
685         # Maximum interval between pulses (maximum pitch period)
686         if .maxInt <= 0
687                 .maxInt = 0.02
688         endif
689         .margin = 8*.maxInt
690         select .sourceSound
691         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
692         select .targetSound
693         .targetName$ = replace_regex$(selected$(), " ", "_", 0)
695         # Iterate over target pulses
696         select .targetPulses
697         .numPulses = Get number of points
698         for .p to .numPulses
699                 # Target
700                 select .targetPulses
701                 .tTarget = Get time from index: .p
702                 .pLeft = Get interval: .tTarget - 0.001
703                 .pRight = Get interval: .tTarget + 0.001
704                 # Source
705                 select .sourcePulses
706                 .q = Get nearest index: .tTarget
707                 .tSource = Get time from index: .q
708                 .qLeft = Get interval: .tSource - 0.001
709                 .qRight = Get interval: .tSource + 0.001
710                 # Gaussian window parameters (FWHM Left and Right)
711                 # FWHM = 2*sqrt(2*ln(2)) * c
712                 .cL = min(.pLeft,.qLeft)/(2*sqrt(2*ln(2)))
713                 .cR = min(.pRight,.qRight)/(2*sqrt(2*ln(2)))
714                 if not( .cL = undefined or .cL > .maxInt/(2*sqrt(2*ln(2))) or .cR = undefined or .cR > .maxInt/(2*sqrt(2*ln(2))) )
715                         # Copy one window
716                         select .targetSound
717                         Formula (part): .tTarget-.margin, .tTarget+.margin, 1, 1, "if x<.tTarget then self + '.sourceName$'((x - .tTarget) + .tSource)*exp(-1*(((x - .tTarget)/.cL)^2)/2) else self + '.sourceName$'((x - .tTarget) + .tSource)*exp(-1*(((x - .tTarget)/.cR)^2)/2) endif"
718                 endif
719         endfor
720         select .targetSound
722 endproc
724 # Test overlap_add
725 procedure test_overlap_add .sourceAudio .sourcePulses .vuvTextGrid .targetPulses .newShimmer
726         # Use overlap-add to add new source intervals
727         # Copy only voiced pulses
728         call set_VUV_to_zero .targetPulses .vuvTextGrid U
729         # Create a copy of the old source with voiced parts zeroed
730         select .sourceAudio
731         .testSource = Copy: "OaAsound"
732         call set_VUV_to_zero .testSource .vuvTextGrid V
734         # Copy the voiced parts of the new source to the zeroed voiced parts of the old source
735         call overlap_add .sourceAudio .sourcePulses .testSource .targetPulses 0.02
736         call increase_shimmer .testSource .targetPulses .vuvTextGrid .newShimmer
737         .newSound = selected("Sound")
738         Scale intensity: global.setIntensity
740         select .testSource
741         Remove
742         
743         select .newSound
744         
745 endproc
747 # Set intervals matching a label text to Zero or remove the pulses
748 # Works on Sound and Pulses
749 procedure set_VUV_to_zero .sound .vuvTextGrid .zeroLabel$
750         select .sound
751         .objectType$ = selected$()
752         .objectType$ = extractWord$ (.objectType$, "")
753         select .vuvTextGrid
754         .numIntervals = Get number of intervals: 1
755         # Zero out VU intervals
756         for .i to .numIntervals
757                 select .vuvTextGrid
758                 .vuvLabel$ = Get label of interval: 1, .i
759                 .start = Get starting point: 1, .i
760                 .end = Get end point: 1, .i
761                 if .vuvLabel$ = .zeroLabel$
762                         select .sound
763                         if .objectType$ = "Sound"
764                                 Set part to zero: .start, .end, "at nearest zero crossing"
765                         elsif .objectType$ = "PointProcess"
766                                 Remove points between: .start, .end
767                         else
768                                 printline Unsupported object type for set_VUV_to_zero
769                         endif
770                 endif
771         endfor
772         select .sound
773 endproc
777 # Create a new Bubbles source file from an existing source file
779 # Uses only a randomly selected fraction of the source pulses
781 procedure create_source_bubbles .sourceSound .sourcePulses .fraction
782         # Normalize fraction: <0, 1]. values > 1 are considered %
783         if .fraction <= 0
784                 .fraction = 1
785         elsif .fraction > 1 and .fraction <= 100
786                 .fraction = .fraction / 100
787         elsif .fraction <= 1000
788                 .fraction = .fraction / 1000
789         else
790                 .fraction = 1
791         endif
792         
793         # Create new pulses
794         select .sourcePulses
795         .targetPulses = Copy: "Target Pulses"
796         if .fraction < 1
797                 .numPulses = Get number of points
798                 .discard = 1 - .fraction
799                 for .p to .numPulses
800                         .rand = randomUniform (0, 1)
801                         if .rand < .discard
802                                 select .targetPulses
803                                 Remove point: .p
804                         endif
805                 endfor
806         endif
808         call overlap_add '.sourceSound' '.sourcePulses' -1 '.targetPulses' 0.02
809         .targetSound = overlap_add.targetSound
810         select .targetPulses
811         Remove
812         
813         select .targetSound
814 endproc
816 procedure add_source_bubbles .sound .pulses .fraction .snr .bubblesAudioName$
817         # Get filter
818         select .sound
819         .duration = Get total duration
820         .samplingFrequency = Get sampling frequency
821         if .fraction > 1
822                 .fraction = 1
823         endif
824         if .fraction > 0
825                 # Get filter
826                 select .sound
827                 .targetIntensity = Get intensity (dB)
828                 .targetDuration = Get total duration
829                 .downsampled = Resample: 10000, 50
830                 .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
831                 plus .downsampled
832                 .source = Filter (inverse)
833                 .sourceInt = To Intensity: 70, 0, "yes"
834                 .sourceIntTier = To IntensityTier (peaks)
835                 select .sourceInt
836                 plus .downsampled
837                 plus .source
838                 Remove
839                 
840                 # Create the taget file
841                 .masterSourceSound = Read from file: .bubblesAudioName$
842                 .masterDuration = Get total duration
843                 while .masterDuration < 2*.duration
844                         .tmpA = .masterSourceSound
845                         .tmpB = Copy: "tmpB"
846                         plus .tmpA
847                         .masterSourceSound = Concatenate
848                         selectObject: .tmpA, .tmpB
849                         Remove
850                         select .masterSourceSound
851                         .masterDuration = Get total duration
852                 endwhile
853                 # Get a random start point
854                 .startPoint = randomUniform (0, .duration)
855                 select .masterSourceSound
856                 .targetSound = Extract part: .startPoint, .startPoint+.duration, "rectangular", 1, "no"
857                 Rename: "Bubbles"
858                 select .masterSourceSound
859                 Remove
860                 
861                 select .targetSound
862                 plus .sourceIntTier
863                 .scaledBubbleSource = Multiply: "yes"
864                 Scale intensity: .targetIntensity - .snr
865                 
866                 selectObject: .targetSound, .sourceIntTier
867                 Remove
868                 
869                 select .scaledBubbleSource
870                 plus .lpc
871                 .filteredBubbleSource = Filter: "no"
872                 Rename: "TargetBubbleSound"
873                 .targetSound = Resample: .samplingFrequency, 50
874                 
875                 selectObject: .scaledBubbleSource, .lpc, .filteredBubbleSource
876                 Remove
877                 
878                 select .targetSound
879                 Scale intensity: .targetIntensity - .snr
880         else
881                 .targetSound = Create Sound: "Bubbles", 0, .duration, .samplingFrequency, "0"   
882         endif
883         
884         select .targetSound
885 endproc
888 # Add bubbles
889 # Select a random puls in the bubbles and add it to a random puls in the target
891 # Creates a sound with only the bubbles
893 procedure add_bubbles .sound .rate .snr .vuvTextGrid .bubblesAudioName$
894         # Get filter
895         select .sound
896         .targetIntensity = Get intensity (dB)
897         .targetDuration = Get total duration
898         .tagetSamplingFrequency = Get sampling frequency
899         .targetNumBubbles = .rate * .targetDuration
900         .downsampled = Resample: 10000, 50
901         .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
902         plus .downsampled
903         .source = Filter (inverse)
904         .sourceInt = To Intensity: 70, 0, "yes"
905         .sourceIntTier = To IntensityTier (peaks)
906         select .sourceInt
907         plus .downsampled
908         plus .source
909         Remove
910         
911         if .rate <= 0
912                 .additiveBubblesSound = Create Sound: "Bubbles", 0, .targetDuration, .tagetSamplingFrequency, "0"       
913                 goto EXITBUBBLES
914         endif
915         
916         # Create an empty sound to receive the bubbles
917         .bubblesAudio = Read from file: .bubblesAudioName$
918         .bubblesTextGridName$ = replace_regex$(.bubblesAudioName$, "\.[a-z0-9]{2,}$", ".TextGrid", 0)
919         .bubblesTextGrid = Read from file: .bubblesTextGridName$
920         select .bubblesAudio
921         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
922         .bubblesSamplingFrequency = Get sampling frequency
923         .bubblesIntensity = Get intensity (dB)
924         .bubbleSound = Create Sound: "Bubbles", 0, .targetDuration, .bubblesSamplingFrequency, "0"
925         
926         # Fill the new Bubbles
927         select .bubblesTextGrid
928         .numIntervals = Get number of intervals: 1
929         .bubblesFound = 0
930         while .bubblesFound < .targetNumBubbles
931                 .i = randomInteger(1, .numIntervals)
932                 select .bubblesTextGrid
933                 .label$ = Get label of interval: 1, .i
934                 if .label$ = "sounding"
935                         .bubblesFound += 1
936                         .startPoint = Get starting point: 1, .i
937                         .endPoint = Get end point: 1, .i
938                         .midPoint = (.startPoint + .endPoint)/2
939                         .bubbleDuration = .endPoint - .startPoint
940                         
941                         # Get random insertion point
942                         .t = randomUniform (0.001, .targetDuration-0.001)
943                         .targetStart = .t - .bubbleDuration/2
944                         .targetEnd = .t + .bubbleDuration/2
945                         select .bubbleSound
946                         Formula (part): .targetStart, .targetEnd, 1, 1, "self + '.sourceName$'((x - .t) + .midPoint)"
947                 endif
948         endwhile
950         # Convert selected bubbles to scaled source
951         select .bubbleSound
952         .resampledBubbleSound = Resample: .tagetSamplingFrequency, 50
953         plus .sourceIntTier
954         .scaledBubbleSource = Multiply: "yes"
955         call set_VUV_to_zero .scaledBubbleSource .vuvTextGrid U
956         
957         # The measured Intensity of the few selected bubbles can be too low. Correct for scaling
958         select .scaledBubbleSource
959         .bubbleSoundIntensity = Get intensity (dB)
960         .attenuation = .bubblesIntensity - .bubbleSoundIntensity
961         if .attenuation = undefined
962                 .attenuation = 0
963         endif
964         
965         # Scale bubble sounds
966         select .scaledBubbleSource
967         Scale intensity: .targetIntensity - .snr - .attenuation
968         
969         select .scaledBubbleSource
970         plus .lpc
971         .filteredBubbles = Filter: "no"
972         Rename: "FilteredBubbleNoise"
973         .additiveBubblesSound = Resample: .tagetSamplingFrequency, 50
975         # Clean up
976         select .resampledBubbleSound
977         plus .scaledBubbleSource
978         plus .filteredBubbles
979         plus .lpc
980         plus .sourceIntTier
981         plus .bubblesAudio
982         plus .bubblesTextGrid
983         plus .bubbleSound
984         Remove
985         
986         label EXITBUBBLES
988         select .additiveBubblesSound
989 endproc
991 procedure add_single_bubble .sourceAudio .sourcePulses .sourceI .targetAudio .targetPulses .targetI
992         .margin = 1
993         select .sourceAudio
994         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
995         select .targetAudio
996         .targetName$ = replace_regex$(selected$(), " ", "_", 0)
998         # Target
999         select .targetPulses
1000         .tTarget = Get time from index: .targetI
1001         .pLeft = Get interval: .tTarget - 0.001
1002         .pRight = Get interval: .tTarget + 0.001
1003         
1004         # Source
1005         select .sourcePulses
1006         .tSource = Get time from index: .sourceI
1007         .qLeft = Get interval: .tSource - 0.001
1008         .qRight = Get interval: .tSource + 0.001
1009         
1010         # Gaussian window parameters (FWHM Left and Right)
1011         # FWHM = 2*sqrt(2*ln(2)) * c
1012         .c = (.qLeft+.qRight)/(2*sqrt(2*ln(2)))
1013         if not( .cL = undefined or .cR = undefined)
1014                 # Copy one window
1015                 select .targetAudio
1016                 Formula (part): .tTarget-.margin, .tTarget+.margin, 1, 1, "self + '.sourceName$'((x - .tTarget) + .tSource)*exp(-1*(((x - .tTarget)/.c)^2)/2)"
1017         endif
1018 endproc