From a79c729d42e99cce8dd34edf294ebc986e34e4d2 Mon Sep 17 00:00:00 2001 From: Rob van Son Date: Fri, 9 Dec 2016 15:50:08 +0100 Subject: [PATCH] New attempt to creacky voice detection in third tones --- ToneProt/SGC_ToneProt.praat | 105 ++++++++++++++++++++++++----------------- ToneProt/ToneRecognition.praat | 4 -- ToneProt/ToneRules.praat | 89 ++++++++++++---------------------- ToneProt/ToneScript.praat | 22 +++++++-- 4 files changed, 109 insertions(+), 111 deletions(-) diff --git a/ToneProt/SGC_ToneProt.praat b/ToneProt/SGC_ToneProt.praat index 3505fdc..4baac96 100644 --- a/ToneProt/SGC_ToneProt.praat +++ b/ToneProt/SGC_ToneProt.praat @@ -139,10 +139,14 @@ procedure sgc_ToneProt sgc_ToneProt.currentSound$ sgc_ToneProt.pinyin$ sgc_ToneP ################################################################ toneProt.creackyThree = 0 if index(sgc_ToneProt.pinyin$, "3") + .creakCO = 0.025 + .delta = 0.0000001 + # Lowest point for tone 3 is 1 octave and 3st below the top line # The cut-off is 0,05 quantile of model + 1/2 semitones + select te.recordedPitch + .recordedPitchTier = Down to PitchTier - .creakCO = 0.030 # Calculate Shimmer&Jitter select Sound Source .sound = selected("Sound") @@ -151,8 +155,7 @@ procedure sgc_ToneProt sgc_ToneProt.currentSound$ sgc_ToneProt.pinyin$ sgc_ToneP # Determine the low F0 part of the 3rd tone # Generate word with voiceless low 3rd tone - .newPinyin$ = replace_regex$(sgc_ToneProt.pinyin$, "3(?=[^0-9]+3)", "2", 0) - .newPinyin$ = replace$(.newPinyin$, "3", "9", 0) + .newPinyin$ = replace$(sgc_ToneProt.pinyin$, "3", "9", 0) call generateWord Pitch '.newPinyin$' 'sgc_ToneProt.register' select Pitch '.newPinyin$' .generatedWord9 = selected("Pitch") @@ -162,63 +165,62 @@ procedure sgc_ToneProt sgc_ToneProt.currentSound$ sgc_ToneProt.pinyin$ sgc_ToneP # Create a tier with low part of the third tone as intervals call generateWord TextGrid 'sgc_ToneProt.pinyin$' 'sgc_ToneProt.register' - .lowPresent = 0 select TextGrid 'sgc_ToneProt.pinyin$' - .pitchTier = selected("TextGrid") - .numIntervals = Get number of intervals: 1 - for .int to .numIntervals - select .pitchTier - .label$ = Get label of interval: 1, .int - if .label$ = "3" or .label$ = "9" - .lowPresent += 1 - Set interval text: 1, .int, "3onset" - .start = Get starting point: 1, .int - .end = Get end point: 1, .int - .newStart = .start + (.end - .start)/3 - .newEnd = .start + 5*(.end - .start)/6 - # Add interval to TextGrid - select .pitchTier - Insert boundary: 1, .newStart - Insert boundary: 1, .newEnd - Set interval text: 1, .int+1, "3Low" - Set interval text: 1, .int+2, "3offset" - # Dangerous stuff! - .int += 2 - endif - endfor + .pitchTextGrid = selected("TextGrid") + .lowPresent = Count intervals where: 1, "is equal to", "3" # DTW of .generatedWord9 with sourcePitch if .lowPresent > 0 select te.recordedPitch plus .generatedWord3 .dtw3 = noprogress To DTW... 24 10 yes yes no restriction - Rename... DTW'.generatedWord9' + Rename... DTW'sgc_ToneProt.pinyin$' .distance3 = Get distance (weighted) select te.recordedPitch plus .generatedWord9 - .dtw9 = noprogress To DTW... 24 10 yes yes no restriction - Rename... DTW'.generatedWord9' + .dtw9 = To DTW... 24 10 yes yes no restriction + Rename... DTW'.newPinyin$' .distance9 = Get distance (weighted) - .dtw = .dtw3 - if .dtw3 > .dtw9 - .dtw = .dtw9 + select .pitchTextGrid + if .distance3 <= .distance9 + plus .dtw3 + else + plus .dtw9 endif - select .pitchTier + .origTextGrid = To TextGrid (warp times) .numPitchIntervals = Get number of intervals: 1 for .i to .numPitchIntervals - select .pitchTier + select .origTextGrid .label$ = Get label of interval: 1, .i - if .label$ = "3Low" - .modelStart = Get starting point: 1, .i - .modelEnd = Get end point: 1, .i - select .dtw - .origStart = Get x time from y time: .modelStart - .origEnd = Get x time from y time: .modelEnd + if .label$ = "3" + select .origTextGrid + .threeStart = Get starting point: 1, .i + .threeEnd = Get end point: 1, .i + .lowStart = .threeStart + (.threeEnd - .threeStart)/3 + .lowEnd = .threeStart + 5*(.threeEnd - .threeStart)/6 select .jitterShimmer - .mean = Get mean: 0, .origStart, .origEnd + .mean = Get mean: 0, .lowStart, .lowEnd if .mean > .creakCO toneProt.creackyThree += 1; + + # Replace creacky voice with low pitch in PitchTier + # Use minimumModelFzero + select .recordedPitchTier + # Store begin and endvalues of pitch + .startValue = Get value at time: .lowStart + .endValue = Get value at time: .lowEnd + + # Remove all pitch points between start and end + Remove points between: .lowStart, .lowEnd + + # Add begin and en values at start and end times + Add point: .lowStart, .startValue + Add point: .lowEnd, .endValue + + # Interpolate or add low pitch values in between start and end times if the interval is long + Add point: .lowStart + .delta, minimumModelFzero + Add point: .lowEnd - .delta, minimumModelFzero endif endif endfor @@ -229,10 +231,27 @@ procedure sgc_ToneProt sgc_ToneProt.currentSound$ sgc_ToneProt.pinyin$ sgc_ToneP # Weigh creacky voice by number of syllables toneProt.creackyThree /= toneScript.syllableCount endif - + + # If creacky voice was detected, create new Pitch from adapted PitchTier + if toneProt.creackyThree + # Convert new Pitchtier to Pitch object (based on old Pitch object) + select te.recordedPitch + plus .recordedPitchTier + .newPitch = To Pitch + # Move new Pitch object to old Pitch object ID + select te.recordedPitch + .pitchName$ = selected$("Pitch") + Remove + te.recordedPitch = .newPitch + select te.recordedPitch + Rename: .pitchName$ + .newPitch = 0 + endif +#pause # Clean up select .jitterShimmer - plus .pitchTier + plus .pitchTextGrid + plus .recordedPitchTier plus .generatedWord9 Remove endif diff --git a/ToneProt/ToneRecognition.praat b/ToneProt/ToneRecognition.praat index 7941b82..a33b3df 100644 --- a/ToneProt/ToneRecognition.praat +++ b/ToneProt/ToneRecognition.praat @@ -96,10 +96,6 @@ procedure FreeToneRecognition .pinyin$ .test_word$ .exclude$ .upperRegister .fre .sumSqrDistance = .sumSqrDistance + distance^2 .inFile$ = replace$(.inFile$, "9", "3", 0) - # Creaky voice in 3rd tone is a bonus, maximal 1/2*distance - if toneProt.creackyThree - distance /= toneProt.creackyThree + 1 - endif if .pinyin$ = .inFile$ .correctDistance = distance endif diff --git a/ToneProt/ToneRules.praat b/ToneProt/ToneRules.praat index c9c904c..6e758ee 100644 --- a/ToneProt/ToneRules.praat +++ b/ToneProt/ToneRules.praat @@ -5,7 +5,7 @@ # (which they are). # # toneScript.toneSyllable is the tone number on the current syllable -# 1-4, 0=neutral, 6=Dutch (garbage) intonation +# 1-4, 0=neutral, 6=Dutch (garbage) intonation, 9=3, but with an unvoiced low segment # # These procedures set the following pareameters # and use them to create a Pitch Tier: @@ -36,7 +36,7 @@ procedure toneDuration .zeroToneFactor = 0.5 if toneScript.prevTone = 2 .zeroToneFactor = 0.8 * .zeroToneFactor - elsif toneScript.prevTone = 3 + elsif toneScript.prevTone = 3 or toneScript.prevTone = 9 .zeroToneFactor = 1.1 * .zeroToneFactor elsif toneScript.prevTone = 4 .zeroToneFactor = 0.8 * .zeroToneFactor @@ -44,7 +44,7 @@ procedure toneDuration toneScript.toneFactor = .zeroToneFactor * toneScript.toneFactor elsif toneScript.toneSyllable = 2 toneScript.toneFactor = 0.8 - elsif toneScript.toneSyllable = 3 + elsif toneScript.toneSyllable = 3 or toneScript.toneSyllable = 9 toneScript.toneFactor = 1.1 elsif toneScript.toneSyllable = 4 toneScript.toneFactor = 0.8 @@ -107,7 +107,7 @@ procedure toneRules # Go lower if previous tone is 1 if toneScript.prevTone = 1 toneRules.startPoint = toneRules.startPoint * toneScript.oneSemit - elsif toneScript.prevTone = 4 or toneScript.prevTone = 3 + elsif toneScript.prevTone = 4 or toneScript.prevTone = 3 or toneScript.prevTone = 9 # Special case: 2 following 4 or 3 # Go 1 semitone up toneRules.startPoint = toneScript.lastFrequency / toneScript.oneSemit @@ -128,49 +128,7 @@ procedure toneRules toneScript.point = toneScript.point + (toneScript.voicedDuration)*2/3 Add point... 'toneScript.point' 'toneScript.endPoint' # Third tone - elsif toneScript.toneSyllable = 3 - # Halfway the range - toneRules.startPoint = toneRules.levelThree - toneRules.lowestPoint = toneRules.levelOne * toneScript.threeSemit - # Protect pitch against "underflow" - if toneRules.lowestPoint < toneScript.absoluteMinimum - toneRules.lowestPoint = toneScript.absoluteMinimum - endif - # First syllable - if toneScript.nextTone < 0 - toneScript.endPoint = toneRules.startPoint - # Anticipate rise in next tone - elsif toneScript.nextTone = 1 or toneScript.nextTone = 4 - toneRules.lowestPoint = toneRules.levelOne / toneScript.twoSemit - toneScript.endPoint = toneRules.startPoint - # Anticipate rise in next tone and stay low - elsif toneScript.nextTone = 2 - toneRules.lowestPoint = toneRules.levelOne / toneScript.twoSemit - toneScript.endPoint = toneRules.lowestPoint - # Last one was low, don't go so much lower - elsif toneScript.prevTone = 4 - toneRules.lowestPoint = toneRules.levelOne * toneScript.oneSemit - # Anticipate rise in next tone and stay low - elsif toneScript.nextTone = 0 - toneRules.lowestPoint = toneRules.levelOne - toneScript.endPoint = toneRules.lowestPoint / toneScript.sixSemit - else - toneScript.endPoint = toneRules.startPoint - endif - - # Write toneScript.points - Add point... 'toneScript.point' 'toneRules.startPoint' - # Go 1/3 of the duration down - toneScript.point = toneScript.point + (toneScript.voicedDuration)*2/6 - Add point... 'toneScript.point' 'toneRules.lowestPoint' - # Go half the duration low - toneScript.point = toneScript.point + (toneScript.voicedDuration)*3/6 - Add point... 'toneScript.point' 'toneRules.lowestPoint' - # Return in 1/6th of the duration - toneScript.point = toneScript.point + (toneScript.voicedDuration)*1/6 - Add point... 'toneScript.point' 'toneScript.endPoint' - # Third tone with voice break - elsif toneScript.toneSyllable = 9 + elsif toneScript.toneSyllable = 3 or toneScript.toneSyllable = 9 # Halfway the range toneRules.startPoint = toneRules.levelThree toneRules.lowestPoint = toneRules.levelOne * toneScript.threeSemit @@ -206,22 +164,33 @@ procedure toneRules toneScript.point = toneScript.point + (toneScript.voicedDuration)*2/6 Add point... 'toneScript.point' 'toneRules.lowestPoint' - # voiceless break - .delta = 'toneScript.point' + 0.001 - Add point... '.delta' 0 - - # Go half the duration low - toneScript.point = toneScript.point + (toneScript.voicedDuration)*3/6 - - # voiceless break - .delta = 'toneScript.point' - 0.001 - Add point... '.delta' 0 - Add point... 'toneScript.point' 'toneRules.lowestPoint' + # Third tone with voice break + if toneScript.toneSyllable = 9 + # voiceless break + .delta = 'toneScript.point' + 0.001 + Add point... '.delta' 0 + endif + + # Go half the duration low + toneScript.point = toneScript.point + (toneScript.voicedDuration)*3/6 + + # Third tone with voice break + if toneScript.toneSyllable = 9 + # voiceless break + # voiceless break + .delta = 'toneScript.point' - 0.001 + Add point... '.delta' 0 + endif + + # Add final point of low (voiced) + Add point... 'toneScript.point' 'toneRules.lowestPoint' + # Return in 1/6th of the duration toneScript.point = toneScript.point + (toneScript.voicedDuration)*1/6 # Go on Add point... 'toneScript.point' 'toneScript.endPoint' + # Fourth tone elsif toneScript.toneSyllable = 4 # Start higher than tone 1 (by 2 semitones) @@ -255,7 +224,7 @@ procedure toneRules toneRules.startPoint = toneScript.lastFrequency * toneScript.twoSemit elsif toneScript.prevTone = 2 toneRules.startPoint = toneScript.lastFrequency - elsif toneScript.prevTone = 3 + elsif toneScript.prevTone = 3 or toneScript.prevTone = 9 toneRules.startPoint = toneScript.lastFrequency / toneScript.oneSemit elsif toneScript.prevTone = 4 toneRules.startPoint = toneScript.lastFrequency * toneScript.oneSemit @@ -276,7 +245,7 @@ procedure toneRules # toneRules.midPoint = toneRules.startPoint * toneScript.fiveSemit toneScript.endPoint = toneRules.midPoint * toneScript.twoSemit - elsif toneScript.prevTone = 3 + elsif toneScript.prevTone = 3 or toneScript.prevTone = 9 toneRules.midPoint = toneRules.startPoint / toneScript.twoSemit toneScript.endPoint = toneRules.midPoint elsif toneScript.prevTone = 4 diff --git a/ToneProt/ToneScript.praat b/ToneProt/ToneScript.praat index d2a5b6d..44065ab 100644 --- a/ToneProt/ToneScript.praat +++ b/ToneProt/ToneScript.praat @@ -71,6 +71,10 @@ procedure toneScript toneScript.inputWord$ toneScript.upperRegister toneScript.r # Clean up input if toneScript.inputWord$ <> "" toneScript.inputWord$ = replace_regex$(toneScript.inputWord$, "^\s*(.+)\s*$", "\1", 1) + toneScript.inputWord$ = replace_regex$(toneScript.inputWord$, "5", "0", 0) + # Missing neutral tones + call add_missing_neutral_tones 'toneScript.inputWord$' + toneScript.inputWord$ = add_missing_neutral_tones.pinyin$ endif # Add a tone movement. The current time toneScript.point is 'toneScript.point' @@ -230,6 +234,9 @@ procedure addToneMovement .syllable$ toneScript.topLine toneScript.prevTone tone if toneScript.toneSyllable = 3 and toneScript.nextTone = 3 toneScript.toneSyllable = 2 endif + if toneScript.toneSyllable = 9 and toneScript.nextTone = 9 + toneScript.toneSyllable = 2 + endif # Get voicing pattern toneScript.voicingSyllable$ = "" @@ -276,6 +283,7 @@ procedure wordToTones .wordInput$ toneScript.highPitch .length = 2 * toneScript.margin # Split syllables + toneScript.prevTone = -1 while rindex_regex(.currentRest$, "^[^\d]+[\d]+") > 0 toneScript.syllableCount += 1 syllable'toneScript.syllableCount'$ = replace_regex$(.currentRest$, "^([^\d]+[\d]+)(.*)$", "\1", 1) @@ -311,10 +319,11 @@ procedure wordToTones .wordInput$ toneScript.highPitch if toneScript.syllableCount > 2000 exit endif + toneScript.prevTone = toneScript.currentTone endwhile - + # Create tone pattern - toneScript.pitchTier = Create PitchTier... '.wordInput$' 0 '.length' + toneScript.pitchTier = Create PitchTier: .wordInput$, 0, .length toneScript.pitchTierWord$ = .wordInput$ # Create TextGrid with syllables toneScript.textGrid = Create TextGrid: 0, .length, "Tones", "" @@ -352,6 +361,11 @@ procedure wordToTones .wordInput$ toneScript.highPitch toneScript.lastTone = toneScript.currentTone # Add interval to TextGrid + .writeTone = toneScript.currentTone + # + if (toneScript.currentTone = 3 or toneScript.currentTone = 9) and toneScript.currentTone = toneScript.followTone + .writeTone = 2 + endif select toneScript.textGrid if toneScript.unvoicedDuration > 0 Insert boundary: 1, .lastPoint + toneScript.unvoicedDuration @@ -360,7 +374,7 @@ procedure wordToTones .wordInput$ toneScript.highPitch endif Insert boundary: 1, toneScript.point .int += 1 - Set interval text: 1, .int, "'toneScript.currentTone'" + Set interval text: 1, .int, "'.writeTone'" endfor # Add end toneScript.margin @@ -416,7 +430,7 @@ procedure generateWord toneScript.whatToGenerate$ toneScript.theWord$ toneScript .modelSound = Read from file... 'preferencesAppDir$'/pitchmodels/'toneScript.theWord$'.wav select .modelSound # Third tones get really low - if index(toneScript.theWord$, "3") > 0 + if index_regex(toneScript.theWord$, "3") > 0 call convert2Pitch 20 600 .modelPitch = Kill octave jumps select convert2Pitch.object -- 2.11.4.GIT