Added Test script
[TEvoiceconversion.git] / VoiceConversion.praat
blob1eacfe2f1667a1da68f8e00002a9769538436b5d
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 5 (= rate)
11         real Bubbles_SNR 15 (= 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       Rate of bubble sounds added (per second). Select random bubbles from bubbles.wav&bubbles.TextGrid
72 # Bubbles SNR   Signal to Noise ratio of bubble sounds added (use bubbles.wav)
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$'Rate of bubble sounds added (per second). Select random bubbles from bubbles.wav&bubbles.TextGrid
106         printline Bubbles SNR'tab$''tab$'Signal to Noise ratio of bubble sounds added (use bubbles.wav)
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 .thresshold = -voicing_floor
132 # Scale intensity:
133 select .recordedSound
134 global.setIntensity = Get intensity (dB)
136 call convert_speechOverlapAndAdd  .recordedSound .thresshold jitter shimmer pitch pitch_SD duration hNR bubbles bubbles_SNR
138 # Definitions of functions
140 # Main functions
141 procedure convert_speechOverlapAndAdd .recordedSound .thresshold .jitter .shimmer .pitch .pitch_SD .durationFactor .newHNR .bubble_rate .bubble_snr
142         call change_pitch_duration .recordedSound .pitch .pitch_SD .durationFactor
143         .newPitchSound = selected("Sound")
144         
145         call extractVoicingParameters .newPitchSound .thresshold
146         .recordedTextGrid = selected("TextGrid")
147         .recordedPulses = selected("PointProcess")
148         .recordedInt = selected("Intensity")
149         
150         select .newPitchSound
151         .recordedPulsesPeaks = To PointProcess (periodic, peaks): 60, 300, "yes", "yes"
153         call create_additive_noise .newPitchSound .newHNR .recordedTextGrid
154         .additiveNoise = selected("Sound")
155         
156         call add_bubbles .newPitchSound .bubble_rate .bubble_snr .recordedTextGrid 'bubblesAudioName$'
157         .additiveBubbles = selected("Sound")
158         
159         # Change Jitter, use CC to determine jitter and Peaks to change the periods
160         selectObject: .recordedPulsesPeaks
161         .newPointProcess = Copy: "New_Pulses"
162         call set_jitter .jitter .newPointProcess .recordedPulses
163         call test_overlap_add .newPitchSound .recordedPulsesPeaks .recordedTextGrid .newPointProcess .shimmer
164         .newSound = selected("Sound")
165         
166         # Debug tests
167         if debug
168                 # Old numbers
169                 selectObject: .recordedPulses
170                 .old_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
171                 selectObject: .newPointProcess
172                 .newPPjitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
173                 selectObject: .recordedSound
174                 plus .recordedPulses
175                 .old_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
176                 .old_shimmer = Get shimmer (local): 0.0001, 0.03, 2
177                 
178                 
179                 selectObject: .newSound
180                 .pointP = To PointProcess (periodic, cc): 60, 300
181                 .new_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
183                 selectObject: .newSound
184                 plus .pointP
185                 .new_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
186                 .new_shimmer = Get shimmer (local): 0.0001, 0.03, 2
188                 appendInfoLine:  "New Jitter: '.new_jitter:1%' ('.old_jitter:1%' ~> '.newPPjitter:1%')"
189                 appendInfoLine:  "New Shimmer: '.new_shimmer:1%' ('.old_shimmer:1%')"
190                 
191                 selectObject: .old_amplitude, .pointP, .new_amplitude
192                 Remove
193         endif
194         
195         # Add noise to result
196         call add_sounds .newSound .additiveNoise
197         .resultNoise = selected("Sound")
198         Rename: "NewSpeech"
199         
200         # Add bubbles to result
201         call add_sounds .resultNoise .additiveBubbles
202         .result = selected("Sound")
203         Rename: "NewSpeech"
204         
205         # Clean up
206         selectObject: .newPitchSound, .recordedTextGrid, .recordedPulses, .recordedInt, .newPointProcess, .recordedPulsesPeaks, .newSound, .additiveNoise, .additiveBubbles, .resultNoise
207         Remove
208         
209         selectObject: .result
210 endproc
212 procedure change_pitch_duration .sound .pitch .pitchFraction .durationFactor
213         select .sound
214         .duration = Get total duration
215         
216         .manipulation = To Manipulation: 0.01, 70, 300
217         .pitchTier = Extract pitch tier
218         .currentPitch = Get mean (points): 0, 0
219         .pitch_SD = .pitchFraction / 100 * .pitch
220         
221         select .manipulation
222         .durationTier = Extract duration tier
223         
224         # Change duration
225         if .durationFactor > 0
226                 .numPoints = Get number of points
227                 if .numPoints <= 0
228                         Add point: 0, 1
229                 endif
230                 Formula: "self*'.durationFactor'"
231                 select .manipulation
232                 plus .durationTier
233                 Replace duration tier
234         endif
235         
236         if .pitch > 0
237                 select .pitchTier
238                 .factor = (.pitch / .currentPitch)
239                 Multiply frequencies: 0, .duration, .factor
240                 .currentSD = Get standard deviation (points): 0, 0
241                 
242                 if .currentSD > 0
243                         .factor = .pitch_SD / .currentSD
244                         Formula: "'.pitch' + (self - '.pitch') * '.factor'"
245                 endif
247                 select .manipulation
248                 plus .pitchTier
249                 Replace pitch tier
250         endif
251         
252         .newSound = -1
253         if .currentPitch > 0 or .durationFactor > 0
254                 select .manipulation
255                 .newSound = Get resynthesis (overlap-add)
256         else
257                 select .sound 
258                 .newSound = Copy: "New Sound"
259         endif
260         select .manipulation
261         plus .pitchTier
262         plus .durationTier
263         Remove
264         
265         select .newSound
266 endproc
267         
268 procedure extractVoicingParameters .recordedSound .thresshold
269         # The lowest level of voiced sounds
270         select .recordedSound
271         .pointPcc = To PointProcess (periodic, cc): 60, 300
272         Rename: "RecordedPulses"
273         .textGrid = To TextGrid (vuv): 0.02, 0.01
274         Rename: "RecordedVoicing"
275         .numIntervals = Get number of intervals: 1
277         # Correct voicing boundaries
278         select .recordedSound
279         .intensity = To Intensity: 100, 0, "yes"
280         Rename: "RecordedIntensity"
281         .silences = To TextGrid (silences): .thresshold, 0.1, 0.05, "silent", "sounding"
283         # Start boundaries
284         for .i to .numIntervals
285                 select .textGrid
286                 .label$ = Get label of interval: 1, .i
287                 if .label$ = "V"
288                         .start = Get starting point: 1, .i
289                         .end = Get end point: 1, .i
290                         
291                         # Starting point of voiced interval
292                         select .silences
293                         .s = Get interval at time: 1, .start
294                         .sLabel$ = Get label of interval: 1, .s
295                         if .sLabel$ = "silent"
296                                 .sStart = Get starting point: 1, .s
297                                 .sEnd = Get end point: 1, .s
298                                 select .textGrid
299                                 if .sEnd < .end
300                                         Set interval text: 1, .i, "U"
301                                         # Shift boundaries: Insert&Remove
302                                         Insert boundary: 1, .sEnd
303                                         Set interval text: 1, .i+1, "V"
304                                         if .i > 1
305                                                 Set interval text: 1, .i, ""
306                                                 Remove left boundary: 1, .i
307                                         endif
308                                 else
309                                         # Low intensity, unvoiced
310                                         Set interval text: 1, .i, "U"
311                                 endif
312                         endif
313                 endif
314         endfor
315         
316         # End boundaries
317         for .i to .numIntervals
318                 select .textGrid
319                 .label$ = Get label of interval: 1, .i
320                 if .label$ = "V"
321                         .start = Get starting point: 1, .i
322                         .end = Get end point: 1, .i
323                         
324                         # Starting point of voiced interval
325                         select .silences
326                         .s = Get interval at time: 1, .end
327                         .sLabel$ = Get label of interval: 1, .s
328                         if .sLabel$ = "silent"
329                                 .sStart = Get starting point: 1, .s
330                                 .sEnd = Get end point: 1, .s
331                                 select .textGrid
332                                 if .sStart > .start
333                                         Set interval text: 1, .i, "U"
334                                         # Shift boundaries: Insert&Remove
335                                         Insert boundary: 1, .sStart
336                                         Set interval text: 1, .i, "V"
337                                         if .i > 1
338                                                 Set interval text: 1, .i+1, ""
339                                                 Remove right boundary: 1, .i+1
340                                         endif
341                                 else
342                                         # Low intensity, unvoiced
343                                         Set interval text: 1, .i, "U"
344                                 endif
345                         endif
346                 endif
347         endfor
348         
349         select .silences
350         Remove
351         
352         select .textGrid
353         plus .pointPcc
354         plus .intensity
355 endproc
357 procedure create_additive_noise .sound .newHNR .vuvTextGrid
358         select .sound
359         .duration = Get total duration
360         .sampleFreq = Get sampling frequency
362         .additiveNoiseSound = -1
363         if .newHNR > 0
364                 # Determine noise level
365                 select .sound
366                 .originalIntensity = Get intensity (dB)
367                 .additiveNoise = .originalIntensity - .newHNR
368                 
369                 # Get filter
370                 select .sound
371                 .downsampled = Resample: 10000, 50
372                 .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
373                 plus .downsampled
374                 .source = Filter (inverse)
375                 .sourceInt = To Intensity: 70, 0, "yes"
376                 .sourceIntTier = To IntensityTier (peaks)
378                 # Create additive noise
379                 if .additiveNoise > 0
380                         .noise = Create Sound from formula: "WhiteNoise", 1, 0, .duration, .sampleFreq, "randomGauss(0,0.1)"
381                         plus .lpc
382                         .filteredNoise = Filter: "no"
383                         plus .sourceIntTier
384                         .additiveNoiseSoundTMP = Multiply: "yes"
385                         call set_VUV_to_zero .additiveNoiseSoundTMP .vuvTextGrid U
386                         Scale intensity: .additiveNoise
387                         .additiveNoiseSound = Resample: .sampleFreq, 50
388                         
389                         selectObject: .noise, .filteredNoise, .additiveNoiseSoundTMP
390                         Remove
391                 endif
392                 
393                 selectObject: .downsampled, .lpc, .source, .sourceInt, .sourceIntTier
394                 Remove
395                 
396         endif
397         
398         if .additiveNoiseSound <= 0
399                 .additiveNoiseSound = Create Sound from formula: "AdditiveNoise", 1, 0, .duration, .sampleFreq, "0"
400         endif
401         
402         select .additiveNoiseSound
403 endproc
405 procedure add_sounds .sound1 .sound2
406         selectObject: .sound1, .sound2
407         .stereo = Combine to stereo
408         .addedSound = Convert to mono
409         
410         select .stereo
411         Remove
412         
413         selectObject: .addedSound
414 endproc
417 # Set Jitter to a specified number
419 # Ti = ti - ti-1 (interval i)
420 # Jitter (absolute)  is Sum[ abs(Ti - Ti-1) ] / N-1
421 # Jitter = Jitter (absolute) / mean(Ti)
423 # For a Normal distribution
424 # E(|X|) = sqrt(2/pi) * stdev(X)
426 # E(Ti - Ti-1) = 0
427 # E(Ti^2) = var(Ti) + E(Ti)^2
428 # E(Ti*Ti-1) = cor(Ti, Ti-1) + E(Ti)^2
429 # var(Ti - Ti-1) = E(Ti^2 - 2*Ti*Ti-1 + Ti-1^2)
430 #                = 2*E(Ti^2) - 2*E(Ti*Ti-1)
431 #                = 2*[ var(Ti) * (1 - cor(Ti, Ti-1)) ]
433 # Combine, assuming a Normal distribution:
434 # Jitter = E(|Ti - Ti-1|) / E(Ti)
435 #        = sqrt(2/pi) * stdev(Ti - Ti-1) / mean(Ti)
436 #        = sqrt(2/pi * var(Ti - Ti-1)) / mean(Ti)
437 #        = sqrt[ 4/pi * ( var(Ti) * (1 - cor(Ti, Ti-1)) ) ] / mean(Ti)
439 # Change Ti -> T'i; Jitter -> a*Jitter while keeping mean(Ti) = mean(T'i) constant
440 # ei = (ti + ti-2)/2 - ti-1
441 # Jitter' = a * Jitter
442 #         = a * sqrt[ 4/pi * var(Ti - Ti-1) ] / mean(Ti)
444 # => var(T'i - T'i-1) = a^2 * var(Ti - Ti-1) 
445 #                     = a^2 * E[ (Ti - Ti-1)^2 ]
446 #                     = a^2 * E[ (ti - ti-1 - ti-1 + ti-2)^2 ]
447 #                     = a^2 * 2 * E[ ((ti + ti-2)/2 - ti-1)^2 ]
448 #                     = a^2 * 2 * E[ ei^2 ]
449 #                     = 2 * E[ (a*ei)^2 ]
450 #                     = 2 * var(ei')
452 # Generalizing, var(T'i - T'i-1) = 2*(var(ti-1) + var(ti) + var(ti+1))
453 # To increase Jitter -> Jitter'
454 # 1) Determine var(Ti - Ti-1) = (Jitter * mean(Ti))^2 * pi / 2
455 # 2) Calculate var(T'i - T'i-1) = (Jitter' * mean(T'i))^2 * pi / 2
456 # 3) Determine var to add: 
457 #    add_var(Ti - Ti-1) = var(T'i - T'i-1) - var(Ti - Ti-1)
458 # 4) Var of Noise to add per ti: add_var(ti) = add_var(Ti - Ti-1)/(2*3)
459 # 5) Sd of Noise to add per ti: add_sd(ti) = sqrt(add_var(ti))
461 # .newJitter is in %
462 # Converts .pulses into pulses with new Jitter
463 procedure set_jitter .newJitter .pulses .pulsesCC
464         
465         if .pulses > 0 and .newJitter > 0
466                 .newJitter /= 100
467                 select .pulses
468                 # Use CC to determine real jitter
469                 if .pulsesCC > 0
470                         select .pulsesCC
471                 endif
472                 .current_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
473                 .current_abs_jitter = Get jitter (local, absolute): 0, 0, 0.0001, 0.03, 2
474                 .current_mean_period = Get mean period: 0, 0, 0.0001, 0.03, 2
475                 .current_stdev_period = Get stdev period: 0, 0, 0.0001, 0.03, 2
477                 if .newJitter > .current_jitter
478                         .current_var = .current_abs_jitter**2 * pi/2
479                         .end_var = (.newJitter * .current_mean_period)**2 * pi/2
480                         # The variance to add per boundary (total / (2*3))
481                         .add_var = (.end_var - .current_var) / 6
482                         .stdev_e = sqrt(.add_var)
483                         
484                         # Keep the original pulses just is case the order of the pulses might change
485                         select .pulses
486                         .origPulses = Copy: "Original_Pulses"
487                         .numPoints = Get number of points
488                         
489                         # New jitter
490                         # Change jitter by moving the ti according to 
491                         # t'i = ti - randomGauss(0, stdev(e'))
492                         for .p from 1 to .numPoints
493                                 select .origPulses
494                                 .t = Get time from index: .p
495                                 .new_t = .t - randomGauss(0, .stdev_e)
496                 
497                                 # Remove current point 
498                                 select .pulses
499                                 .r = Get nearest index: .t
500                                 Remove point: .r
501                                 Add point: .new_t
502                         endfor
503                         
504                         select .origPulses
505                         Remove
506                 else
507                         pause New jitter: '.newJitter' must be larger than current jitter '.current_jitter:4'
508                 endif
509                 
510                 # Calculate new jitter
511                 select .pulses
512                 .jitter_new = Get jitter (local): 0, 0, 0.0001, 0.03, 2
513                 .jitter_new *= 100
514                 .current_jitter *= 100
515         endif
516         
517         select .pulses
518 endproc
521 # We cannot use the shimmer of a sentence, so we can only "add" shimmer
523 # .new_shimmer is in %
524 # .sound: Source Sound
525 # .pulses: PointProcess
526 # .voicing: VUV TextGrid
527 # .new_shimmer: New shimmer in %
528 procedure increase_shimmer .sound .pulses .voicing .newShimmer
529         if .newShimmer > 0
530                 .newShimmer /= 100
531                 .shimmer_new = 0
532                 
533                 # Create Amplitude Tier and get current shimmer
534                 select .sound
535                 .duration = Get total duration
536                 plus .pulses
537                 .current_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
538                 .current_shimmer = Get shimmer (local): 0.0001, 0.03, 2
539                 select .current_amplitude
540                 .numPoints = Get number of points
541                 .ampreal = Down to TableOfReal
542                 .sumamp = 0
543                 .n = 0
544                 for .p from 1 to .numPoints
545                         select .ampreal
546                         .tmp = Get value: .p, 2
547                         if .tmp > 0
548                                 .sumamp += .tmp
549                                 .n += 1
550                         endif
551                 endfor
552                 .meanAmp = .sumamp / .n
553                 
554                 # Sd must be multiplied with the amplitude
555                 if .newShimmer > .current_shimmer
556                         .new_var = (.newShimmer**2 - .current_shimmer**2) * .meanAmp**2 * pi / 2
557                 else
558                         .new_var = .newShimmer**2 * .meanAmp**2 * pi / 2
559                 endif
560                 if .new_var > 0 
561                         .new_sd = sqrt(.new_var / 2)
562                 else
563                         .new_sd = 0
564                 endif
565                 
566                 .new_amplitude = Create AmplitudeTier: "New_Amplitude", 0, .duration
567                 for .p from 1 to .numPoints
568                         select .ampreal
569                         .t = Get value: .p, 1
570                         .a = Get value: .p, 2
571                         if .a = undefined
572                                 .a = 0
573                         endif
574                         if .a > 0
575                                 .new_a = .a - randomGauss(0, .new_sd)
576                                 if .new_a < 0 
577                                         .new_a = 0
578                                 endif
579                                 
580                                 # Add new value
581                                 select .new_amplitude
582                                 Add point: .t, .new_a / .a
583                         else
584                                 Add point: .t, .a
585                         endif
586                 endfor
587                 
588                 # Set unvoiced parts to 1
589                 select .new_amplitude
590                 Add point: 0, 1
591         
592                 select .voicing
593                 .numIntervals = Get number of intervals: 1
594                 for .i from 1 to .numIntervals
595                         select .voicing
596                         .t = Get end point: 1, .i
597                         select .new_amplitude
598                         Add point: .t, 1
599                 endfor
600                 
601                 select .ampreal
602                 plus .current_amplitude
603                 Remove
604                 
605                 # Overlay shimmer over sound
606                 select .sound
607                 plus .new_amplitude
608                 .new_sound = Multiply
609                 Rename: "NewSound_Shimmer"
610                 
611                 select .new_sound
612                 plus .pulses
613                 .shimmer_new = Get shimmer (local): 0, 0, 0.0001, 0.02, 1.3, 1.6
614                 .shimmer_new *= 100
615                 .current_shimmer *= 100
616                 .newShimmer *= 100
617         
618                 select .new_amplitude
619                 Remove
620                 
621         else
622                 select .sound
623                 .new_sound = Copy: "NewSound_Shimmer"
624         endif
625         
626         select .new_sound
627 endproc
629 # Make a copy of the source to the target matching the pulses in source and target
630 # Copies fragments around pulses in sourcePulses under the direction of the 
631 # corresponding pulses in targetPulses using the Overlap&Add method (Gaussian window)
633 # Ignores voiceless parts, ie, intervals between pulses > .maxInt
634 # For voices, .maxInt should be ~0.02 (F0 > 50Hz). For other sounds, e.g., bubbles, this
635 # should be increased to fit the whole sound between pulses.
637 # Midpoint between the pulses, periods add up to a factor of ~1.04. 
638 # At the pulses themselves, it adds up to ~1.12 (summed left and right)
640 procedure overlap_add .sourceSound .sourcePulses .targetSound .targetPulses .maxInt
641         # Maximum interval between pulses (maximum pitch period)
642         if .maxInt <= 0
643                 .maxInt = 0.02
644         endif
645         .margin = 8*.maxInt
646         select .sourceSound
647         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
648         select .targetSound
649         .targetName$ = replace_regex$(selected$(), " ", "_", 0)
651         # Iterate over target pulses
652         select .targetPulses
653         .numPulses = Get number of points
654         for .p to .numPulses
655                 # Target
656                 select .targetPulses
657                 .tTarget = Get time from index: .p
658                 .pLeft = Get interval: .tTarget - 0.001
659                 .pRight = Get interval: .tTarget + 0.001
660                 # Source
661                 select .sourcePulses
662                 .q = Get nearest index: .tTarget
663                 .tSource = Get time from index: .q
664                 .qLeft = Get interval: .tSource - 0.001
665                 .qRight = Get interval: .tSource + 0.001
666                 # Gaussian window parameters (FWHM Left and Right)
667                 # FWHM = 2*sqrt(2*ln(2)) * c
668                 .cL = min(.pLeft,.qLeft)/(2*sqrt(2*ln(2)))
669                 .cR = min(.pRight,.qRight)/(2*sqrt(2*ln(2)))
670                 if not( .cL = undefined or .cL > .maxInt/(2*sqrt(2*ln(2))) or .cR = undefined or .cR > .maxInt/(2*sqrt(2*ln(2))) )
671                         # Copy one window
672                         select .targetSound
673                         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"
674                 endif
675         endfor
677 endproc
679 # Test overlap_add
680 procedure test_overlap_add .sourceAudio .sourcePulses .vuvTextGrid .targetPulses .newShimmer
681         # Use overlap-add to add new source intervals
682         # Copy only voiced pulses
683         call set_VUV_to_zero .targetPulses .vuvTextGrid U
684         # Create a copy of the old source with voiced parts zeroed
685         select .sourceAudio
686         .testSource = Copy: "OaAsound"
687         call set_VUV_to_zero .testSource .vuvTextGrid V
689         # Copy the voiced parts of the new source to the zeroed voiced parts of the old source
690         call overlap_add .sourceAudio .sourcePulses .testSource .targetPulses 0.02
691         call increase_shimmer .testSource .targetPulses .vuvTextGrid .newShimmer
692         .newSound = selected("Sound")
693         Scale intensity: global.setIntensity
695         select .testSource
696         Remove
697         
698         select .newSound
699         
700 endproc
702 # Set intervals matching a label text to Zero or remove the pulses
703 # Works on Sound and Pulses
704 procedure set_VUV_to_zero .sound .vuvTextGrid .zeroLabel$
705         select .sound
706         .objectType$ = selected$()
707         .objectType$ = extractWord$ (.objectType$, "")
708         select .vuvTextGrid
709         .numIntervals = Get number of intervals: 1
710         # Zero out VU intervals
711         for .i to .numIntervals
712                 select .vuvTextGrid
713                 .vuvLabel$ = Get label of interval: 1, .i
714                 .start = Get starting point: 1, .i
715                 .end = Get end point: 1, .i
716                 if .vuvLabel$ = .zeroLabel$
717                         select .sound
718                         if .objectType$ = "Sound"
719                                 Set part to zero: .start, .end, "at nearest zero crossing"
720                         elsif .objectType$ = "PointProcess"
721                                 Remove points between: .start, .end
722                         else
723                                 printline Unsupported object type for set_VUV_to_zero
724                         endif
725                 endif
726         endfor
727         select .sound
728 endproc
732 # Add bubbles
735 # Add bubbles
736 # Select a random puls in the bubbles and add it to a random puls in the target
738 # Creates a sound with only the bubbles
740 procedure add_bubbles .sound .rate .snr .vuvTextGrid .bubblesAudioName$
741         # Get filter
742         select .sound
743         .targetIntensity = Get intensity (dB)
744         .targetDuration = Get total duration
745         .tagetSamplingFrequency = Get sampling frequency
746         .targetNumBubbles = .rate * .targetDuration
747         .downsampled = Resample: 10000, 50
748         .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
749         plus .downsampled
750         .source = Filter (inverse)
751         .sourceInt = To Intensity: 70, 0, "yes"
752         .sourceIntTier = To IntensityTier (peaks)
753         select .sourceInt
754         plus .downsampled
755         plus .source
756         Remove
757         
758         if .rate <= 0
759                 .additiveBubblesSound = Create Sound: "Bubbles", 0, .targetDuration, .tagetSamplingFrequency, "0"       
760                 goto EXITBUBBLES
761         endif
762         
763         # Create an empty sound to receive the bubbles
764         .bubblesAudio = Read from file: .bubblesAudioName$
765         .bubblesTextGridName$ = replace_regex$(.bubblesAudioName$, "\.[a-z0-9]{2,}$", ".TextGrid", 0)
766         .bubblesTextGrid = Read from file: .bubblesTextGridName$
767         select .bubblesAudio
768         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
769         .bubblesSamplingFrequency = Get sampling frequency
770         .bubblesIntensity = Get intensity (dB)
771         .bubbleSound = Create Sound: "Bubbles", 0, .targetDuration, .bubblesSamplingFrequency, "0"
772         
773         # Fill the new Bubbles
774         select .bubblesTextGrid
775         .numIntervals = Get number of intervals: 1
776         .bubblesFound = 0
777         while .bubblesFound < .targetNumBubbles
778                 .i = randomInteger(1, .numIntervals)
779                 select .bubblesTextGrid
780                 .label$ = Get label of interval: 1, .i
781                 if .label$ = "sounding"
782                         .bubblesFound += 1
783                         .startPoint = Get starting point: 1, .i
784                         .endPoint = Get end point: 1, .i
785                         .midPoint = (.startPoint + .endPoint)/2
786                         .bubbleDuration = .endPoint - .startPoint
787                         
788                         # Get random insertion point
789                         .t = randomUniform (0.001, .targetDuration-0.001)
790                         .targetStart = .t - .bubbleDuration/2
791                         .targetEnd = .t + .bubbleDuration/2
792                         select .bubbleSound
793                         Formula (part): .targetStart, .targetEnd, 1, 1, "self + '.sourceName$'((x - .t) + .midPoint)"
794                 endif
795         endwhile
797         # Convert selected bubbles to scaled source
798         select .bubbleSound
799         .resampledBubbleSound = Resample: .tagetSamplingFrequency, 50
800         plus .sourceIntTier
801         .scaledBubbleSource = Multiply: "yes"
802         call set_VUV_to_zero .scaledBubbleSource .vuvTextGrid U
803         
804         # The measured Intensity of the few selected bubbles can be too low. Correct for scaling
805         select .scaledBubbleSource
806         .bubbleSoundIntensity = Get intensity (dB)
807         .attenuation = .bubblesIntensity - .bubbleSoundIntensity
808         if .attenuation = undefined
809                 .attenuation = 0
810         endif
811         
812         # Scale bubble sounds
813         select .scaledBubbleSource
814         Scale intensity: .targetIntensity - .snr - .attenuation
815         
816         select .scaledBubbleSource
817         plus .lpc
818         .filteredBubbles = Filter: "no"
819         Rename: "FilteredBubbleNoise"
820         .additiveBubblesSound = Resample: .tagetSamplingFrequency, 50
822         # Clean up
823         select .resampledBubbleSound
824         plus .scaledBubbleSource
825         plus .filteredBubbles
826         plus .lpc
827         plus .sourceIntTier
828         plus .bubblesAudio
829         plus .bubblesTextGrid
830         plus .bubbleSound
831         Remove
832         
833         label EXITBUBBLES
835         select .additiveBubblesSound
836 endproc
838 procedure add_single_bubble .sourceAudio .sourcePulses .sourceI .targetAudio .targetPulses .targetI
839         .margin = 1
840         select .sourceAudio
841         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
842         select .targetAudio
843         .targetName$ = replace_regex$(selected$(), " ", "_", 0)
845         # Target
846         select .targetPulses
847         .tTarget = Get time from index: .targetI
848         .pLeft = Get interval: .tTarget - 0.001
849         .pRight = Get interval: .tTarget + 0.001
850         
851         # Source
852         select .sourcePulses
853         .tSource = Get time from index: .sourceI
854         .qLeft = Get interval: .tSource - 0.001
855         .qRight = Get interval: .tSource + 0.001
856         
857         # Gaussian window parameters (FWHM Left and Right)
858         # FWHM = 2*sqrt(2*ln(2)) * c
859         .c = (.qLeft+.qRight)/(2*sqrt(2*ln(2)))
860         if not( .cL = undefined or .cR = undefined)
861                 # Copy one window
862                 select .targetAudio
863                 Formula (part): .tTarget-.margin, .tTarget+.margin, 1, 1, "self + '.sourceName$'((x - .tTarget) + .tSource)*exp(-1*(((x - .tTarget)/.c)^2)/2)"
864         endif
865 endproc