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