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