Skip to content

Commit

Permalink
feat: add toZeroCrossings method to textgrid tiers; remove method in …
Browse files Browse the repository at this point in the history
…praatio_scripts
  • Loading branch information
timmahrt committed Feb 19, 2023
1 parent 3272def commit f3baa56
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 91 deletions.
3 changes: 3 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ splitAudioOnTier has a slightly new signature
- None -> "name_and_label"
- "noPartialIntervals" -> "allowPartialIntervals" to avoid a double negative

tgBoundariesToZeroCrossings was removed
- use the new TextgridTier.toZeroCrossings() instead

### interval_tier.py

insertSpace raises CollisionError rather than ArgumentError when the space to insert overlaps
Expand Down
8 changes: 7 additions & 1 deletion examples/delete_vowels.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ def deleteVowels(inputTGFN, inputWavFN, outputPath, doShrink, atZeroCrossing=Tru

tg = textgrid.openTextgrid(inputTGFN, False)

praatio_scripts.tgBoundariesToZeroCrossings(tg, wav, zeroCrossingTGFN)
outputTg = textgrid.Textgrid()
for tier in tg.tiers:
newTier = tier.toZeroCrossings(inputWavFN)
outputTg.addTier(newTier)

tg = outputTg

outputTg.save(zeroCrossingTGFN, "short_textgrid", True)
else:
tg = textgrid.openTextgrid(inputTGFN, False)

Expand Down
13 changes: 13 additions & 0 deletions praatio/data_classes/interval_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CropCollision,
)

from praatio import audio
from praatio.utilities import errors
from praatio.utilities import utils
from praatio.utilities import constants
Expand Down Expand Up @@ -701,6 +702,18 @@ def morph(

return IntervalTier(self.name, newEntryList, newMin, newMax)

def toZeroCrossings(self, wavFN: str) -> "IntervalTier":
"""Moves all timestamps to the nearest zero crossing"""
wav = audio.QueryWav(wavFN)

intervals = []
for start, end, label in self.entries:
newStart = wav.findNearestZeroCrossing(start)
newStop = wav.findNearestZeroCrossing(end)
intervals.append(Interval(newStart, newStop, label))

return self.new(entries=intervals)

def validate(
self, reportingMode: Literal["silence", "warning", "error"] = "warning"
) -> bool:
Expand Down
3 changes: 3 additions & 0 deletions praatio/data_classes/klattgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ def insertEntry(self):
def insertSpace(self):
raise NotImplementedError

def toZeroCrossings(self):
raise NotImplementedError

def validate(self):
raise NotImplementedError

Expand Down
12 changes: 12 additions & 0 deletions praatio/data_classes/point_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from typing_extensions import Literal

from praatio import audio
from praatio.utilities.constants import (
Point,
POINT_TIER,
Expand Down Expand Up @@ -361,6 +362,17 @@ def insertSpace(

return newTier

def toZeroCrossings(self, wavFN: str) -> "PointTier":
"""Moves all timestamps to the nearest zero crossing"""
wav = audio.QueryWav(wavFN)

points = []
for time, label in self.entries:
newTime = wav.findNearestZeroCrossing(time)
points.append(Point(newTime, label))

return self.new(entries=points)

def validate(
self, reportingMode: Literal["silence", "warning", "error"] = "warning"
) -> bool:
Expand Down
4 changes: 4 additions & 0 deletions praatio/data_classes/textgrid_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ def insertSpace(
def deleteEntry(self, entry) -> None: # pragma: no cover
pass

@abstractmethod
def toZeroCrossings(self, wavFN) -> "TextgridTier": # pragma: no cover
pass

@abstractmethod
def validate(self, reportingMode) -> bool: # pragma: no cover
pass
38 changes: 0 additions & 38 deletions praatio/praatio_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,44 +264,6 @@ def splitTierEntries(
return tg


def tgBoundariesToZeroCrossings(
tg: textgrid.Textgrid,
wav: audio.Wav,
adjustPointTiers: bool = True,
adjustIntervalTiers: bool = True,
) -> textgrid.Textgrid:
"""Makes all textgrid interval boundaries fall on pressure wave zero crossings
adjustPointTiers: if True, point tiers will be adjusted.
adjustIntervalTiers: if True, interval tiers will be adjusted.
"""
for tier in tg.tiers:
newTier: textgrid_tier.TextgridTier
if isinstance(tier, textgrid.PointTier):
if adjustPointTiers is False:
continue

points = []
for start, label in tier.entries:
newStart = wav.findNearestZeroCrossing(start)
points.append(Point(newStart, label))
newTier = tier.new(entries=points)
elif isinstance(tier, textgrid.IntervalTier):
if adjustIntervalTiers is False:
continue

intervals = []
for start, end, label in tier.entries:
newStart = wav.findNearestZeroCrossing(start)
newStop = wav.findNearestZeroCrossing(end)
intervals.append(Interval(newStart, newStop, label))
newTier = tier.new(entries=intervals)

tg.replaceTier(tier.name, newTier)

return tg


def splitAudioOnTier(
wavFN: str,
tgFN: str,
Expand Down
20 changes: 19 additions & 1 deletion tests/files/bobby.TextGrid
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Object class = "TextGrid"
xmin = 0
xmax = 1.194625
tiers? <exists>
size = 2
size = 3
item []:
item [1]:
class = "IntervalTier"
Expand Down Expand Up @@ -54,3 +54,21 @@ item []:
xmin = 1.1171482864527198
xmax = 1.194625
text = ""
item [3]:
class = "TextTier"
name = "pitch"
xmin = 0
xmax = 1.194625
points: size = 4
points [1]:
number = 0.2807439705332395
mark = "129.8"
points [2]:
number = 0.531643777864549
mark = "75"
points [3]:
number = 0.7019295676778565
mark = "92.81"
points [4]:
number = 0.8794615613130069
mark = "87.46"
112 changes: 71 additions & 41 deletions tests/files/bobby_boundaries_at_zero_crossings.TextGrid
Original file line number Diff line number Diff line change
@@ -1,44 +1,74 @@
File type = "ooTextFile"
Object class = "TextGrid"

0
1.194625
<exists>
2
"IntervalTier"
"word"
0.0124716553288
1.18979591837
6
0.0124716553288
0.06549652777777777
""
0.06549652777777777
0.41062728658536585
"BOBBY"
0.41062728658536585
0.6586395833333334
"RIPPED"
0.6586395833333334
0.7411985997267759
"THE"
0.7411985997267759
1.1171559027777775
"LEDGER"
1.1171559027777775
1.18979591837
""
"IntervalTier"
"phrase"
0
1.194625
3
0
0.06549652777777777
""
0.06549652777777777
1.1171559027777775
"BOBBY RIPPED THE LEDGER"
1.1171559027777775
1.194625
""
xmin = 0
xmax = 1.194625
tiers? <exists>
size = 3
item []:
item [1]:
class = "IntervalTier"
name = "word"
xmin = 0.0124716553288
xmax = 1.18979591837
intervals: size = 6
intervals [1]:
xmin = 0.0124716553288
xmax = 0.06549652777777777
text = ""
intervals [2]:
xmin = 0.06549652777777777
xmax = 0.41062728658536585
text = "BOBBY"
intervals [3]:
xmin = 0.41062728658536585
xmax = 0.6586395833333334
text = "RIPPED"
intervals [4]:
xmin = 0.6586395833333334
xmax = 0.7411985997267759
text = "THE"
intervals [5]:
xmin = 0.7411985997267759
xmax = 1.1171559027777775
text = "LEDGER"
intervals [6]:
xmin = 1.1171559027777775
xmax = 1.18979591837
text = ""
item [2]:
class = "IntervalTier"
name = "phrase"
xmin = 0
xmax = 1.194625
intervals: size = 3
intervals [1]:
xmin = 0
xmax = 0.06549652777777777
text = ""
intervals [2]:
xmin = 0.06549652777777777
xmax = 1.1171559027777775
text = "BOBBY RIPPED THE LEDGER"
intervals [3]:
xmin = 1.1171559027777775
xmax = 1.194625
text = ""
item [3]:
class = "TextTier"
name = "pitch"
xmin = 0
xmax = 1.194625
points: size = 4
points [1]:
number = 0.2787154431216931
mark = "129.8"
points [2]:
number = 0.5304904891304347
mark = "75"
points [3]:
number = 0.7031082057823129
mark = "92.81"
points [4]:
number = 0.8791542174796748
mark = "87.46"
11 changes: 1 addition & 10 deletions tests/test_interval_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -1369,17 +1369,8 @@ def test_to_zero_crossings(self):

sut = originalTier.toZeroCrossings(wavFN)
sut.name = "auto"
expectedTier.name = "manual"
tg.addTier(sut)
tg.addTier(expectedTier)

tg.save(
join(self.outputRoot, "bobby_auto_to_zero_crossings.TextGrid"),
"short_textgrid",
True,
)

# TODO: There are very small differences between praat and praatio's
# TODO: There are small differences between praat and praatio's
# zero-crossing calculations.
self.assertEqual(len(expectedTier.entries), len(sut.entries))
for entry, sutEntry in zip(expectedTier.entries, sut.entries):
Expand Down
21 changes: 21 additions & 0 deletions tests/test_point_tier.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
from os.path import join

from praatio import textgrid
from praatio.utilities.constants import Interval, Point, POINT_TIER
Expand Down Expand Up @@ -400,6 +401,26 @@ def test_insert_space(self):
)
self.assertEqual(predictedPointTier, sut)

def test_to_zero_crossings(self):
wavFN = join(self.dataRoot, "bobby.wav")
tgFN = join(self.dataRoot, "bobby.TextGrid")

tg = textgrid.openTextgrid(tgFN, False)
originalTier = tg.getTier("pitch")

expectedFN = join(self.dataRoot, "bobby_boundaries_at_zero_crossings.TextGrid")
expectedTg = textgrid.openTextgrid(expectedFN, False)
expectedTier = expectedTg.getTier("pitch")

sut = originalTier.toZeroCrossings(wavFN)
sut.name = "auto"

# TODO: There are small differences between praat and praatio's
# zero-crossing calculations.
self.assertEqual(len(expectedTier.entries), len(sut.entries))
for entry, sutEntry in zip(expectedTier.entries, sut.entries):
self.assertAlmostEqual(entry.time, sutEntry.time, 3)

def test_validate_throws_error_if_points_are_not_in_sequence(self):
sut = makePointTier(
points=[Point(1.3, "55"), Point(3.7, "99"), Point(4.5, "32")],
Expand Down

0 comments on commit f3baa56

Please sign in to comment.