Small changes in default
[TEvoiceconversion.git] / VoiceConversion.praat
blob7faf73e648a412264e1a962cd0efb9ffb9e2bc2b
1 #! praat
2
4 form Select audio
5         sentence Recorded_audio aa.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 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       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 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_rate .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         call create_additive_noise .newPitchSound .newHNR .recordedTextGrid
156         .additiveNoise = selected("Sound")
157         
158         call add_bubbles '.newPitchSound' '.bubble_rate' '.bubble_snr' '.recordedTextGrid' 'bubblesAudioName$'
159         .additiveBubbles = selected("Sound")
160         
161         # Change Jitter, use CC to determine jitter and Peaks to change the periods
162         selectObject: .recordedPulsesPeaks
163         .newPointProcess = Copy: "New_Pulses"
164         call set_jitter .jitter .newPointProcess .recordedPulses
165         call test_overlap_add .newPitchSound .recordedPulsesPeaks .recordedTextGrid .newPointProcess .shimmer
166         .newSound = selected("Sound")
167         
168         # Create bubbles from source
169         call add_source_bubbles '.newPitchSound' '.recordedPulses' 0.7 10 'te_source_bubbles_name$'
170         
171         # Debug tests
172         if debug
173                 # Old numbers
174                 selectObject: .recordedPulses
175                 .old_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
176                 selectObject: .newPointProcess
177                 .newPPjitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
178                 selectObject: .recordedSound
179                 plus .recordedPulses
180                 .old_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
181                 .old_shimmer = Get shimmer (local): 0.0001, 0.03, 2
182                 
183                 
184                 selectObject: .newSound
185                 .pointP = To PointProcess (periodic, cc): 60, 300
186                 .new_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
188                 selectObject: .newSound
189                 plus .pointP
190                 .new_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
191                 .new_shimmer = Get shimmer (local): 0.0001, 0.03, 2
193                 appendInfoLine:  "New Jitter: '.new_jitter:1%' ('.old_jitter:1%' ~> '.newPPjitter:1%')"
194                 appendInfoLine:  "New Shimmer: '.new_shimmer:1%' ('.old_shimmer:1%')"
195                 
196                 selectObject: .old_amplitude, .pointP, .new_amplitude
197                 Remove
198         endif
199         
200         # Add noise to result
201         call add_sounds .newSound .additiveNoise
202         .resultNoise = selected("Sound")
203         Rename: "NewSpeech"
204         
205         # Add bubbles to result
206         call add_sounds .resultNoise .additiveBubbles
207         .result = selected("Sound")
208         Rename: "NewSpeech"
209         
210         # Clean up
211         selectObject: .newPitchSound, .recordedTextGrid, .recordedPulses, .recordedInt, .newPointProcess, .recordedPulsesPeaks, .newSound, .additiveNoise, .additiveBubbles, .resultNoise
212         Remove
213         
214         selectObject: .result
215 endproc
217 procedure change_pitch_duration .sound .pitch .pitchFraction .durationFactor
218         select .sound
219         .duration = Get total duration
220         
221         .manipulation = To Manipulation: 0.01, 70, 300
222         .pitchTier = Extract pitch tier
223         .currentPitch = Get mean (points): 0, 0
224         .pitch_SD = .pitchFraction / 100 * .pitch
225         
226         select .manipulation
227         .durationTier = Extract duration tier
228         
229         # Change duration
230         if .durationFactor > 0
231                 .numPoints = Get number of points
232                 if .numPoints <= 0
233                         Add point: 0, 1
234                 endif
235                 Formula: "self*'.durationFactor'"
236                 select .manipulation
237                 plus .durationTier
238                 Replace duration tier
239         endif
240         
241         if .pitch > 0
242                 select .pitchTier
243                 .factor = (.pitch / .currentPitch)
244                 Multiply frequencies: 0, .duration, .factor
245                 .currentSD = Get standard deviation (points): 0, 0
246                 
247                 if .currentSD > 0
248                         .factor = .pitch_SD / .currentSD
249                         Formula: "'.pitch' + (self - '.pitch') * '.factor'"
250                 endif
252                 select .manipulation
253                 plus .pitchTier
254                 Replace pitch tier
255         endif
256         
257         .newSound = -1
258         if .currentPitch > 0 or .durationFactor > 0
259                 select .manipulation
260                 .newSound = Get resynthesis (overlap-add)
261         else
262                 select .sound 
263                 .newSound = Copy: "New Sound"
264         endif
265         select .manipulation
266         plus .pitchTier
267         plus .durationTier
268         Remove
269         
270         select .newSound
271 endproc
272         
273 procedure extractVoicingParameters .recordedSound .thresshold
274         # The lowest level of voiced sounds
275         select .recordedSound
276         .pointPcc = To PointProcess (periodic, cc): 60, 300
277         Rename: "RecordedPulses"
278         .textGrid = To TextGrid (vuv): 0.02, 0.01
279         Rename: "RecordedVoicing"
280         .numIntervals = Get number of intervals: 1
282         # Correct voicing boundaries
283         select .recordedSound
284         .intensity = To Intensity: 100, 0, "yes"
285         Rename: "RecordedIntensity"
286         .silences = To TextGrid (silences): .thresshold, 0.1, 0.05, "silent", "sounding"
288         # Start boundaries
289         for .i to .numIntervals
290                 select .textGrid
291                 .label$ = Get label of interval: 1, .i
292                 if .label$ = "V"
293                         .start = Get starting point: 1, .i
294                         .end = Get end point: 1, .i
295                         
296                         # Starting point of voiced interval
297                         select .silences
298                         .s = Get interval at time: 1, .start
299                         .sLabel$ = Get label of interval: 1, .s
300                         if .sLabel$ = "silent"
301                                 .sStart = Get starting point: 1, .s
302                                 .sEnd = Get end point: 1, .s
303                                 select .textGrid
304                                 if .sEnd < .end
305                                         Set interval text: 1, .i, "U"
306                                         # Shift boundaries: Insert&Remove
307                                         Insert boundary: 1, .sEnd
308                                         Set interval text: 1, .i+1, "V"
309                                         if .i > 1
310                                                 Set interval text: 1, .i, ""
311                                                 Remove left boundary: 1, .i
312                                         endif
313                                 else
314                                         # Low intensity, unvoiced
315                                         Set interval text: 1, .i, "U"
316                                 endif
317                         endif
318                 endif
319         endfor
320         
321         # End boundaries
322         for .i to .numIntervals
323                 select .textGrid
324                 .label$ = Get label of interval: 1, .i
325                 if .label$ = "V"
326                         .start = Get starting point: 1, .i
327                         .end = Get end point: 1, .i
328                         
329                         # Starting point of voiced interval
330                         select .silences
331                         .s = Get interval at time: 1, .end
332                         .sLabel$ = Get label of interval: 1, .s
333                         if .sLabel$ = "silent"
334                                 .sStart = Get starting point: 1, .s
335                                 .sEnd = Get end point: 1, .s
336                                 select .textGrid
337                                 if .sStart > .start
338                                         Set interval text: 1, .i, "U"
339                                         # Shift boundaries: Insert&Remove
340                                         Insert boundary: 1, .sStart
341                                         Set interval text: 1, .i, "V"
342                                         if .i > 1
343                                                 Set interval text: 1, .i+1, ""
344                                                 Remove right boundary: 1, .i+1
345                                         endif
346                                 else
347                                         # Low intensity, unvoiced
348                                         Set interval text: 1, .i, "U"
349                                 endif
350                         endif
351                 endif
352         endfor
353         
354         select .silences
355         Remove
356         
357         select .textGrid
358         plus .pointPcc
359         plus .intensity
360 endproc
362 procedure create_additive_noise .sound .newHNR .vuvTextGrid
363         select .sound
364         .duration = Get total duration
365         .sampleFreq = Get sampling frequency
367         .additiveNoiseSound = -1
368         if .newHNR > 0
369                 # Determine noise level
370                 select .sound
371                 .originalIntensity = Get intensity (dB)
372                 .additiveNoise = .originalIntensity - .newHNR
373                 
374                 # Get filter
375                 select .sound
376                 .downsampled = Resample: 10000, 50
377                 .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
378                 plus .downsampled
379                 .source = Filter (inverse)
380                 .sourceInt = To Intensity: 70, 0, "yes"
381                 .sourceIntTier = To IntensityTier (peaks)
383                 # Create additive noise
384                 if .additiveNoise > 0
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                         Scale intensity: .additiveNoise
392                         .additiveNoiseSound = Resample: .sampleFreq, 50
393                         
394                         selectObject: .noise, .filteredNoise, .additiveNoiseSoundTMP
395                         Remove
396                 endif
397                 
398                 selectObject: .downsampled, .lpc, .source, .sourceInt, .sourceIntTier
399                 Remove
400                 
401         endif
402         
403         if .additiveNoiseSound <= 0
404                 .additiveNoiseSound = Create Sound from formula: "AdditiveNoise", 1, 0, .duration, .sampleFreq, "0"
405         endif
406         
407         select .additiveNoiseSound
408 endproc
410 procedure add_sounds .sound1 .sound2
411         selectObject: .sound1, .sound2
412         .stereo = Combine to stereo
413         .addedSound = Convert to mono
414         
415         select .stereo
416         Remove
417         
418         selectObject: .addedSound
419 endproc
422 # Set Jitter to a specified number
424 # Ti = ti - ti-1 (interval i)
425 # Jitter (absolute)  is Sum[ abs(Ti - Ti-1) ] / N-1
426 # Jitter = Jitter (absolute) / mean(Ti)
428 # For a Normal distribution
429 # E(|X|) = sqrt(2/pi) * stdev(X)
431 # E(Ti - Ti-1) = 0
432 # E(Ti^2) = var(Ti) + E(Ti)^2
433 # E(Ti*Ti-1) = cor(Ti, Ti-1) + E(Ti)^2
434 # var(Ti - Ti-1) = E(Ti^2 - 2*Ti*Ti-1 + Ti-1^2)
435 #                = 2*E(Ti^2) - 2*E(Ti*Ti-1)
436 #                = 2*[ var(Ti) * (1 - cor(Ti, Ti-1)) ]
438 # Combine, assuming a Normal distribution:
439 # Jitter = E(|Ti - Ti-1|) / E(Ti)
440 #        = sqrt(2/pi) * stdev(Ti - Ti-1) / mean(Ti)
441 #        = sqrt(2/pi * var(Ti - Ti-1)) / mean(Ti)
442 #        = sqrt[ 4/pi * ( var(Ti) * (1 - cor(Ti, Ti-1)) ) ] / mean(Ti)
444 # Change Ti -> T'i; Jitter -> a*Jitter while keeping mean(Ti) = mean(T'i) constant
445 # ei = (ti + ti-2)/2 - ti-1
446 # Jitter' = a * Jitter
447 #         = a * sqrt[ 4/pi * var(Ti - Ti-1) ] / mean(Ti)
449 # => var(T'i - T'i-1) = a^2 * var(Ti - Ti-1) 
450 #                     = a^2 * E[ (Ti - Ti-1)^2 ]
451 #                     = a^2 * E[ (ti - ti-1 - ti-1 + ti-2)^2 ]
452 #                     = a^2 * 2 * E[ ((ti + ti-2)/2 - ti-1)^2 ]
453 #                     = a^2 * 2 * E[ ei^2 ]
454 #                     = 2 * E[ (a*ei)^2 ]
455 #                     = 2 * var(ei')
457 # Generalizing, var(T'i - T'i-1) = 2*(var(ti-1) + var(ti) + var(ti+1))
458 # To increase Jitter -> Jitter'
459 # 1) Determine var(Ti - Ti-1) = (Jitter * mean(Ti))^2 * pi / 2
460 # 2) Calculate var(T'i - T'i-1) = (Jitter' * mean(T'i))^2 * pi / 2
461 # 3) Determine var to add: 
462 #    add_var(Ti - Ti-1) = var(T'i - T'i-1) - var(Ti - Ti-1)
463 # 4) Var of Noise to add per ti: add_var(ti) = add_var(Ti - Ti-1)/(2*3)
464 # 5) Sd of Noise to add per ti: add_sd(ti) = sqrt(add_var(ti))
466 # .newJitter is in %
467 # Converts .pulses into pulses with new Jitter
468 procedure set_jitter .newJitter .pulses .pulsesCC
469         
470         if .pulses > 0 and .newJitter > 0
471                 .newJitter /= 100
472                 select .pulses
473                 # Use CC to determine real jitter
474                 if .pulsesCC > 0
475                         select .pulsesCC
476                 endif
477                 .current_jitter = Get jitter (local): 0, 0, 0.0001, 0.03, 2
478                 .current_abs_jitter = Get jitter (local, absolute): 0, 0, 0.0001, 0.03, 2
479                 .current_mean_period = Get mean period: 0, 0, 0.0001, 0.03, 2
480                 .current_stdev_period = Get stdev period: 0, 0, 0.0001, 0.03, 2
482                 if .newJitter > .current_jitter
483                         .current_var = .current_abs_jitter**2 * pi/2
484                         .end_var = (.newJitter * .current_mean_period)**2 * pi/2
485                         # The variance to add per boundary (total / (2*3))
486                         .add_var = (.end_var - .current_var) / 6
487                         .stdev_e = sqrt(.add_var)
488                         
489                         # Keep the original pulses just is case the order of the pulses might change
490                         select .pulses
491                         .origPulses = Copy: "Original_Pulses"
492                         .numPoints = Get number of points
493                         
494                         # New jitter
495                         # Change jitter by moving the ti according to 
496                         # t'i = ti - randomGauss(0, stdev(e'))
497                         for .p from 1 to .numPoints
498                                 select .origPulses
499                                 .t = Get time from index: .p
500                                 .new_t = .t - randomGauss(0, .stdev_e)
501                 
502                                 # Remove current point 
503                                 select .pulses
504                                 .r = Get nearest index: .t
505                                 Remove point: .r
506                                 Add point: .new_t
507                         endfor
508                         
509                         select .origPulses
510                         Remove
511                 else
512                         pause New jitter: '.newJitter' must be larger than current jitter '.current_jitter:4'
513                 endif
514                 
515                 # Calculate new jitter
516                 select .pulses
517                 .jitter_new = Get jitter (local): 0, 0, 0.0001, 0.03, 2
518                 .jitter_new *= 100
519                 .current_jitter *= 100
520         endif
521         
522         select .pulses
523 endproc
526 # We cannot use the shimmer of a sentence, so we can only "add" shimmer
528 # .new_shimmer is in %
529 # .sound: Source Sound
530 # .pulses: PointProcess
531 # .voicing: VUV TextGrid
532 # .new_shimmer: New shimmer in %
533 procedure increase_shimmer .sound .pulses .voicing .newShimmer
534         if .newShimmer > 0
535                 .newShimmer /= 100
536                 .shimmer_new = 0
537                 
538                 # Create Amplitude Tier and get current shimmer
539                 select .sound
540                 .duration = Get total duration
541                 plus .pulses
542                 .current_amplitude = To AmplitudeTier (period): 0, 0, 0.0001, 0.03, 2
543                 .current_shimmer = Get shimmer (local): 0.0001, 0.03, 2
544                 select .current_amplitude
545                 .numPoints = Get number of points
546                 .ampreal = Down to TableOfReal
547                 .sumamp = 0
548                 .n = 0
549                 for .p from 1 to .numPoints
550                         select .ampreal
551                         .tmp = Get value: .p, 2
552                         if .tmp > 0
553                                 .sumamp += .tmp
554                                 .n += 1
555                         endif
556                 endfor
557                 .meanAmp = .sumamp / .n
558                 
559                 # Sd must be multiplied with the amplitude
560                 if .newShimmer > .current_shimmer
561                         .new_var = (.newShimmer**2 - .current_shimmer**2) * .meanAmp**2 * pi / 2
562                 else
563                         .new_var = .newShimmer**2 * .meanAmp**2 * pi / 2
564                 endif
565                 if .new_var > 0 
566                         .new_sd = sqrt(.new_var / 2)
567                 else
568                         .new_sd = 0
569                 endif
570                 
571                 .new_amplitude = Create AmplitudeTier: "New_Amplitude", 0, .duration
572                 for .p from 1 to .numPoints
573                         select .ampreal
574                         .t = Get value: .p, 1
575                         .a = Get value: .p, 2
576                         if .a = undefined
577                                 .a = 0
578                         endif
579                         if .a > 0
580                                 .new_a = .a - randomGauss(0, .new_sd)
581                                 if .new_a < 0 
582                                         .new_a = 0
583                                 endif
584                                 
585                                 # Add new value
586                                 select .new_amplitude
587                                 Add point: .t, .new_a / .a
588                         else
589                                 Add point: .t, .a
590                         endif
591                 endfor
592                 
593                 # Set unvoiced parts to 1
594                 select .new_amplitude
595                 Add point: 0, 1
596         
597                 select .voicing
598                 .numIntervals = Get number of intervals: 1
599                 for .i from 1 to .numIntervals
600                         select .voicing
601                         .t = Get end point: 1, .i
602                         select .new_amplitude
603                         Add point: .t, 1
604                 endfor
605                 
606                 select .ampreal
607                 plus .current_amplitude
608                 Remove
609                 
610                 # Overlay shimmer over sound
611                 select .sound
612                 plus .new_amplitude
613                 .new_sound = Multiply
614                 Rename: "NewSound_Shimmer"
615                 
616                 select .new_sound
617                 plus .pulses
618                 .shimmer_new = Get shimmer (local): 0, 0, 0.0001, 0.02, 1.3, 1.6
619                 .shimmer_new *= 100
620                 .current_shimmer *= 100
621                 .newShimmer *= 100
622         
623                 select .new_amplitude
624                 Remove
625                 
626         else
627                 select .sound
628                 .new_sound = Copy: "NewSound_Shimmer"
629         endif
630         
631         select .new_sound
632 endproc
634 # Make a copy of the source to the target matching the pulses in source and target
635 # Copies fragments around pulses in sourcePulses under the direction of the 
636 # corresponding pulses in targetPulses using the Overlap&Add method (Gaussian window)
638 # Ignores voiceless parts, ie, intervals between pulses > .maxInt
639 # For voices, .maxInt should be ~0.02 (F0 > 50Hz). For other sounds, e.g., bubbles, this
640 # should be increased to fit the whole sound between pulses.
642 # Midpoint between the pulses, periods add up to a factor of ~1.04. 
643 # At the pulses themselves, it adds up to ~1.12 (summed left and right)
645 procedure overlap_add .sourceSound .sourcePulses .targetSound .targetPulses .maxInt
646         # Create empty .targetSound if .targetSound does not exist
647         if .targetSound <= 0
648                 select .sourceSound
649                 .duration = Get total duration
650                 .samplingFrequency = Get sampling frequency
651                 .targetSound = Create Sound from formula: "Target Sound", 1, 0, .duration, .samplingFrequency, "0"
652         endif
653         # Default, just copy the source pulses
654         if .targetPulses <= 0
655                 .targetPulses = .sourcePulses
656         endif
658         # Maximum interval between pulses (maximum pitch period)
659         if .maxInt <= 0
660                 .maxInt = 0.02
661         endif
662         .margin = 8*.maxInt
663         select .sourceSound
664         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
665         select .targetSound
666         .targetName$ = replace_regex$(selected$(), " ", "_", 0)
668         # Iterate over target pulses
669         select .targetPulses
670         .numPulses = Get number of points
671         for .p to .numPulses
672                 # Target
673                 select .targetPulses
674                 .tTarget = Get time from index: .p
675                 .pLeft = Get interval: .tTarget - 0.001
676                 .pRight = Get interval: .tTarget + 0.001
677                 # Source
678                 select .sourcePulses
679                 .q = Get nearest index: .tTarget
680                 .tSource = Get time from index: .q
681                 .qLeft = Get interval: .tSource - 0.001
682                 .qRight = Get interval: .tSource + 0.001
683                 # Gaussian window parameters (FWHM Left and Right)
684                 # FWHM = 2*sqrt(2*ln(2)) * c
685                 .cL = min(.pLeft,.qLeft)/(2*sqrt(2*ln(2)))
686                 .cR = min(.pRight,.qRight)/(2*sqrt(2*ln(2)))
687                 if not( .cL = undefined or .cL > .maxInt/(2*sqrt(2*ln(2))) or .cR = undefined or .cR > .maxInt/(2*sqrt(2*ln(2))) )
688                         # Copy one window
689                         select .targetSound
690                         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"
691                 endif
692         endfor
693         select .targetSound
695 endproc
697 # Test overlap_add
698 procedure test_overlap_add .sourceAudio .sourcePulses .vuvTextGrid .targetPulses .newShimmer
699         # Use overlap-add to add new source intervals
700         # Copy only voiced pulses
701         call set_VUV_to_zero .targetPulses .vuvTextGrid U
702         # Create a copy of the old source with voiced parts zeroed
703         select .sourceAudio
704         .testSource = Copy: "OaAsound"
705         call set_VUV_to_zero .testSource .vuvTextGrid V
707         # Copy the voiced parts of the new source to the zeroed voiced parts of the old source
708         call overlap_add .sourceAudio .sourcePulses .testSource .targetPulses 0.02
709         call increase_shimmer .testSource .targetPulses .vuvTextGrid .newShimmer
710         .newSound = selected("Sound")
711         Scale intensity: global.setIntensity
713         select .testSource
714         Remove
715         
716         select .newSound
717         
718 endproc
720 # Set intervals matching a label text to Zero or remove the pulses
721 # Works on Sound and Pulses
722 procedure set_VUV_to_zero .sound .vuvTextGrid .zeroLabel$
723         select .sound
724         .objectType$ = selected$()
725         .objectType$ = extractWord$ (.objectType$, "")
726         select .vuvTextGrid
727         .numIntervals = Get number of intervals: 1
728         # Zero out VU intervals
729         for .i to .numIntervals
730                 select .vuvTextGrid
731                 .vuvLabel$ = Get label of interval: 1, .i
732                 .start = Get starting point: 1, .i
733                 .end = Get end point: 1, .i
734                 if .vuvLabel$ = .zeroLabel$
735                         select .sound
736                         if .objectType$ = "Sound"
737                                 Set part to zero: .start, .end, "at nearest zero crossing"
738                         elsif .objectType$ = "PointProcess"
739                                 Remove points between: .start, .end
740                         else
741                                 printline Unsupported object type for set_VUV_to_zero
742                         endif
743                 endif
744         endfor
745         select .sound
746 endproc
750 # Create a new Bubbles source file from an existing source file
752 # Uses only a randomly selected fraction of the source pulses
754 procedure create_source_bubbles .sourceSound .sourcePulses .fraction
755         # Normalize fraction: <0, 1]. values > 1 are considered %
756         if .fraction <= 0
757                 .fraction = 1
758         elsif .fraction > 1 and .fraction <= 100
759                 .fraction = .fraction / 100
760         elsif .fraction <= 1000
761                 .fraction = .fraction / 1000
762         else
763                 .fraction = 1
764         endif
765         
766         # Create new pulses
767         select .sourcePulses
768         .targetPulses = Copy: "Target Pulses"
769         if .fraction < 1
770                 .numPulses = Get number of points
771                 .discard = 1 - .fraction
772                 for .p to .numPulses
773                         .rand = randomUniform (0, 1)
774                         if .rand < .discard
775                                 select .targetPulses
776                                 Remove point: .p
777                         endif
778                 endfor
779         endif
781         call overlap_add '.sourceSound' '.sourcePulses' -1 '.targetPulses' 0.02
782         .targetSound = overlap_add.targetSound
783         select .targetPulses
784         Remove
785         
786         select .targetSound
787 endproc
789 procedure add_source_bubbles .sound .pulses .fraction .snr .bubblesAudioName$
790         # Get filter
791         select .sound
792         .duration = Get total duration
793         .targetIntensity = Get intensity (dB)
794         .targetDuration = Get total duration
795         .tagetSamplingFrequency = Get sampling frequency
796         .downsampled = Resample: 10000, 50
797         .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
798         plus .downsampled
799         .source = Filter (inverse)
800         .sourceInt = To Intensity: 70, 0, "yes"
801         .sourceIntTier = To IntensityTier (peaks)
802         select .sourceInt
803         plus .downsampled
804         plus .source
805         Remove
806         
807         # Create the taget file
808         .masterSourceSound = Read from file: .bubblesAudioName$
809         .masterDuration = Get total duration
810         while .masterDuration < 2*.duration
811                 .tmpA = .masterSourceSound
812                 .tmpB = Copy: "tmpB"
813                 plus .tmpA
814                 .masterSourceSound = Concatenate
815                 selectObject: .tmpA, .tmpB
816                 Remove
817                 select .masterSourceSound
818                 .masterDuration = Get total duration
819         endwhile
820         # Get a random start point
821         .startPoint = randomUniform (0, .duration)
822         select .masterSourceSound
823         .sourceSound = Extract part: .startPoint, .startPoint+.duration, "rectangular", 1, "no"
824         select .masterSourceSound
825         Remove
826         # There is a sourceSound of the correct length, copy a fraction of the pulses
827         call create_source_bubbles .sourceSound .pulses .fraction
828         .targetSound = create_source_bubbles.targetSound
829         
830         select .targetSound
831         plus .sourceIntTier
832         .scaledBubbleSource = Multiply: "yes"
833         Scale intensity: .targetIntensity - .snr
834         
835         selectObject: .targetSound, .sourceIntTier, .sourceSound
836         Remove
837         
838         select .scaledBubbleSource
839         plus .lpc
840         .filteredBubbleSource = Filter: "no"
841         Rename: "TargetBubbleSound"
842         .targetSound = Resample: .tagetSamplingFrequency, 50
843         
844         selectObject: .scaledBubbleSource, .lpc, .filteredBubbleSource
845         Remove
846         
847         select .targetSound
848         Scale intensity: .targetIntensity - .snr
849         
850 endproc
853 # Add bubbles
854 # Select a random puls in the bubbles and add it to a random puls in the target
856 # Creates a sound with only the bubbles
858 procedure add_bubbles .sound .rate .snr .vuvTextGrid .bubblesAudioName$
859         # Get filter
860         select .sound
861         .targetIntensity = Get intensity (dB)
862         .targetDuration = Get total duration
863         .tagetSamplingFrequency = Get sampling frequency
864         .targetNumBubbles = .rate * .targetDuration
865         .downsampled = Resample: 10000, 50
866         .lpc = To LPC (autocorrelation): 10, 0.025, 0.005, 50
867         plus .downsampled
868         .source = Filter (inverse)
869         .sourceInt = To Intensity: 70, 0, "yes"
870         .sourceIntTier = To IntensityTier (peaks)
871         select .sourceInt
872         plus .downsampled
873         plus .source
874         Remove
875         
876         if .rate <= 0
877                 .additiveBubblesSound = Create Sound: "Bubbles", 0, .targetDuration, .tagetSamplingFrequency, "0"       
878                 goto EXITBUBBLES
879         endif
880         
881         # Create an empty sound to receive the bubbles
882         .bubblesAudio = Read from file: .bubblesAudioName$
883         .bubblesTextGridName$ = replace_regex$(.bubblesAudioName$, "\.[a-z0-9]{2,}$", ".TextGrid", 0)
884         .bubblesTextGrid = Read from file: .bubblesTextGridName$
885         select .bubblesAudio
886         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
887         .bubblesSamplingFrequency = Get sampling frequency
888         .bubblesIntensity = Get intensity (dB)
889         .bubbleSound = Create Sound: "Bubbles", 0, .targetDuration, .bubblesSamplingFrequency, "0"
890         
891         # Fill the new Bubbles
892         select .bubblesTextGrid
893         .numIntervals = Get number of intervals: 1
894         .bubblesFound = 0
895         while .bubblesFound < .targetNumBubbles
896                 .i = randomInteger(1, .numIntervals)
897                 select .bubblesTextGrid
898                 .label$ = Get label of interval: 1, .i
899                 if .label$ = "sounding"
900                         .bubblesFound += 1
901                         .startPoint = Get starting point: 1, .i
902                         .endPoint = Get end point: 1, .i
903                         .midPoint = (.startPoint + .endPoint)/2
904                         .bubbleDuration = .endPoint - .startPoint
905                         
906                         # Get random insertion point
907                         .t = randomUniform (0.001, .targetDuration-0.001)
908                         .targetStart = .t - .bubbleDuration/2
909                         .targetEnd = .t + .bubbleDuration/2
910                         select .bubbleSound
911                         Formula (part): .targetStart, .targetEnd, 1, 1, "self + '.sourceName$'((x - .t) + .midPoint)"
912                 endif
913         endwhile
915         # Convert selected bubbles to scaled source
916         select .bubbleSound
917         .resampledBubbleSound = Resample: .tagetSamplingFrequency, 50
918         plus .sourceIntTier
919         .scaledBubbleSource = Multiply: "yes"
920         call set_VUV_to_zero .scaledBubbleSource .vuvTextGrid U
921         
922         # The measured Intensity of the few selected bubbles can be too low. Correct for scaling
923         select .scaledBubbleSource
924         .bubbleSoundIntensity = Get intensity (dB)
925         .attenuation = .bubblesIntensity - .bubbleSoundIntensity
926         if .attenuation = undefined
927                 .attenuation = 0
928         endif
929         
930         # Scale bubble sounds
931         select .scaledBubbleSource
932         Scale intensity: .targetIntensity - .snr - .attenuation
933         
934         select .scaledBubbleSource
935         plus .lpc
936         .filteredBubbles = Filter: "no"
937         Rename: "FilteredBubbleNoise"
938         .additiveBubblesSound = Resample: .tagetSamplingFrequency, 50
940         # Clean up
941         select .resampledBubbleSound
942         plus .scaledBubbleSource
943         plus .filteredBubbles
944         plus .lpc
945         plus .sourceIntTier
946         plus .bubblesAudio
947         plus .bubblesTextGrid
948         plus .bubbleSound
949         Remove
950         
951         label EXITBUBBLES
953         select .additiveBubblesSound
954 endproc
956 procedure add_single_bubble .sourceAudio .sourcePulses .sourceI .targetAudio .targetPulses .targetI
957         .margin = 1
958         select .sourceAudio
959         .sourceName$ = replace_regex$(selected$(), " ", "_", 0)
960         select .targetAudio
961         .targetName$ = replace_regex$(selected$(), " ", "_", 0)
963         # Target
964         select .targetPulses
965         .tTarget = Get time from index: .targetI
966         .pLeft = Get interval: .tTarget - 0.001
967         .pRight = Get interval: .tTarget + 0.001
968         
969         # Source
970         select .sourcePulses
971         .tSource = Get time from index: .sourceI
972         .qLeft = Get interval: .tSource - 0.001
973         .qRight = Get interval: .tSource + 0.001
974         
975         # Gaussian window parameters (FWHM Left and Right)
976         # FWHM = 2*sqrt(2*ln(2)) * c
977         .c = (.qLeft+.qRight)/(2*sqrt(2*ln(2)))
978         if not( .cL = undefined or .cR = undefined)
979                 # Copy one window
980                 select .targetAudio
981                 Formula (part): .tTarget-.margin, .tTarget+.margin, 1, 1, "self + '.sourceName$'((x - .tTarget) + .tSource)*exp(-1*(((x - .tTarget)/.c)^2)/2)"
982         endif
983 endproc