Skip to content

Commit

Permalink
Overhaul audio.py (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
timmahrt committed Jan 7, 2023
1 parent 50dbff6 commit b1330dd
Show file tree
Hide file tree
Showing 27 changed files with 1,165 additions and 480 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -32,5 +32,5 @@ var/
*.project
*.pydevproject

test/files/*/
tests/files/*/
examples/files/*/
4 changes: 4 additions & 0 deletions .travis.yml
@@ -1,9 +1,13 @@
dist: jammy
language: python
python:
- "3.7"
- "3.8"
- "3.9"
- "3.11"
install:
- pip install "importlib-metadata>=4.12"
- pip install -U pip setuptools
- python setup.py install
- pip install coveralls
- pip install pytest-cov
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,14 @@

*Praatio uses semantic versioning (Major.Minor.Patch)*

Ver 6.0 (Jan 7, 2023)
- Refactored 'audio.py' for maintainability (see [UPGRADING.md](https://github.com/timmahrt/praatIO/blob/main/UPGRADING.md) for details)
- Added unit tests for 'audio.py'
- Fixed several bugs related to audio.py
- Fixed an issue related to finding zero crossings when a value of zero does not appear in the current sample
- Fixed a rounding issue in several methods (`openAudioFile`, `insert`, `generateSineWave`, `generateSilence`). The rounding error would lead to minor miscalculations. But if the calculations were run many times, the errors would accumulate and become more noticable.
- Fixed an issue with `readFramesAtTimes`. When the parameter `keepList` or `deleteList` was set, the wrong segments would be kept or deleted.

Ver 5.1 (Dec 31, 2021)
- Fuzzy equivalence for timestamps in Intervals and Points

Expand Down
19 changes: 19 additions & 0 deletions DEVELOP.md
@@ -0,0 +1,19 @@

These are development notes for myself

## Documentation

Documentation is generated with the following command:
`pdoc praatio -d google -o docs`

A live version can be seen with
`pdoc praatio -d google`

pdoc will read from praatio, as installed on the computer, so you may need to run `pip install .` if you want to generate documentation from a locally edited version of praatio.

## Tests

Tests are run with

`pytest --cov=praatio tests/`

2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Tim Mahrt
Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Tim Mahrt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
43 changes: 3 additions & 40 deletions README.md
Expand Up @@ -23,7 +23,7 @@ of speech. [Praat can be downloaded here](<http://www.fon.hum.uva.nl/praat/>)
3. [Version History](#version-history)
4. [Requirements](#requirements)
5. [Installation](#installation)
6. [Version 4 to 5 Migration](#version-4-to-5-migration)
6. [Upgrading major versions](#upgrading)
7. [Usage](#usage)
8. [Common Use Cases](#common-use-cases)
9. [Tests](#tests)
Expand Down Expand Up @@ -79,46 +79,9 @@ If python is not in your path, you'll need to enter the full path e.g.

C:\Python37\python.exe setup.py install

## Version 4 to 5 Migration
## Upgrading

Many things changed between versions 4 and 5. If you see an error like
`WARNING: You've tried to import 'tgio' which was renamed 'textgrid' in praatio 5.x.`
it means that you have installed version 5 but your code was written for praatio 4.x or earlier.

The immediate solution is to uninstall praatio 5 and install praatio 4. From the command line:
```
pip uninstall praatio
pip install "praatio<5"
```

If praatio is being installed as a project dependency--ie it is set as a dependency in setup.py like
```
install_requires=["praatio"],
```
then changing it to the following should fix the problem
```
install_requires=["praatio ~= 4.1"],
```

Many files, classes, and functions were renamed in praatio 5 to hopefully be clearer. There
were too many changes to list here but the `tgio` module was renamed `textgrid`.

Also, the interface for `openTextgrid()` and `tg.save()` has changed. Here are examples of the required arguments in the new interface
```
textgrid.openTextgrid(
fn=name,
includeEmptyIntervals=False
)
```
```
tg.save(
fn=name,
format= "short_textgrid",
includeBlankSpaces= False
)
```

Please consult the documentation to help in upgrading to version 5.
Please view [UPGRADING.md](https://github.com/timmahrt/praatIO/blob/main/UPGRADING.md) for detailed information about how to upgrade from earlier versions.

## Usage

Expand Down
84 changes: 84 additions & 0 deletions UPGRADING.md
@@ -0,0 +1,84 @@

This guide outlines steps to upgrading between versions.

For more information about the most recent version of Praatio, please see the documentation linked in the README file.

If you are having difficulty upgrading, please don't hesistate to open an issue or contact me.

# Table of contents
1. [Version 5 to 6](#version-5-to-6-migration)
2. [Version 4 to 5](#version-4-to-5-migration)

## Version 5 to 6 Migration

### audio.py

audio.py has been refreshed in version 6 with numerous bugfixes and
api changes.

Renamed methods and classes
- audio.WavQueryObj -> audio.QueryWav
- audio.WavObj -> audio.Wav
(the new names of these classes are used below)
- audio.samplesAsNums -> audio.convertFromBytes
- audio.numsAsSamples -> audio.convertToBytes
- audio.getMaxAmplitude -> audio.calculateMaxAmplitude
- audio.AbstractWav.getDuration -> audio.AbstractWav.duration
- audio.Wav.getSubsegment -> audio.Wav.getSubwav

Moved methods
- audio.generateSineWave -> audio.AudioGenerator.generateSineWave
- audio.generateSilence -> audio.AudioGenerator.generateSilence
- audio.openAudioFile -> audio.readFramesAtTimes
- audio.WavQuery.concatenate -> audio.Wav.concatenate

Removed methods
- audio.WavQuery.deleteWavSections
- audio.WavQuery.outputModifiedWav
- audio.Wav.getIndexAtTime (made private)
- audio.Wav.insertSilence (use audio.Wav.insert and audio.AudioGenerator.generateSilence instead)

Added methods
- audio.Wav.replaceSegment
- audio.Wav.open

## Version 4 to 5 Migration

Many things changed between versions 4 and 5. If you see an error like
`WARNING: You've tried to import 'tgio' which was renamed 'textgrid' in praatio 5.x.`
it means that you have installed version 5 but your code was written for praatio 4.x or earlier.

The immediate solution is to uninstall praatio 5 and install praatio 4. From the command line:
```
pip uninstall praatio
pip install "praatio<5"
```

If praatio is being installed as a project dependency--ie it is set as a dependency in setup.py like
```
install_requires=["praatio"],
```
then changing it to the following should fix the problem
```
install_requires=["praatio ~= 4.1"],
```

Many files, classes, and functions were renamed in praatio 5 to hopefully be clearer. There
were too many changes to list here but the `tgio` module was renamed `textgrid`.

Also, the interface for `openTextgrid()` and `tg.save()` has changed. Here are examples of the required arguments in the new interface
```
textgrid.openTextgrid(
fn=name,
includeEmptyIntervals=False
)
```
```
tg.save(
fn=name,
format= "short_textgrid",
includeBlankSpaces= False
)
```

Please consult the documentation to help in upgrading to version 5.
8 changes: 6 additions & 2 deletions examples/anonymize_recording.py
Expand Up @@ -36,5 +36,9 @@
deleteList = [(start, end) for start, end, _ in deleteList]

# Replace segments with a sine wave
wavQObj = audio.WavQueryObj(join(path, wavFN))
wavQObj.deleteWavSections(outputWavFN, deleteList=deleteList, operation="sine wave")
wav = audio.Wav.open(join(path, wavFN))
for start, end in deleteList:
sineFrames = audio.AudioGenerator.fromWav(wav).generateSineWave(
end - start, audio.DEFAULT_SINE_FREQUENCY
)
wav.replaceSegment(start, end, sineFrames)
31 changes: 22 additions & 9 deletions examples/delete_vowels.py
Expand Up @@ -5,6 +5,7 @@
import os
from os.path import join
import copy
import wave

from praatio import textgrid
from praatio import praatio_scripts
Expand All @@ -13,7 +14,9 @@


def isVowel(label):
return any([vowel in label.lower() for vowel in ["a", "e", "i", "o", "u"]])
return any(
[vowel in label.lower() for vowel in ["a", "e", "i", "o", "u", "ə", "œ"]]
)


def deleteVowels(inputTGFN, inputWavFN, outputPath, doShrink, atZeroCrossing=True):
Expand All @@ -25,28 +28,38 @@ def deleteVowels(inputTGFN, inputWavFN, outputPath, doShrink, atZeroCrossing=Tru
outputWavFN = join(outputPath, wavFN)
outputTGFN = join(outputPath, tgFN)

wav = audio.QueryWav(inputWavFN)

if atZeroCrossing is True:
zeroCrossingTGPath = join(outputPath, "zero_crossing_tgs")
zeroCrossingTGFN = join(zeroCrossingTGPath, tgFN)
utils.makeDir(zeroCrossingTGPath)

tg = textgrid.openTextgrid(inputTGFN, False)
wavObj = audio.WavQueryObj(inputWavFN)

praatio_scripts.tgBoundariesToZeroCrossings(tg, wavObj, zeroCrossingTGFN)
praatio_scripts.tgBoundariesToZeroCrossings(tg, wav, zeroCrossingTGFN)

else:
tg = textgrid.openTextgrid(inputTGFN, False)

keepList = tg.tierDict["phone"].entryList
keepList = [entry for entry in keepList if not isVowel(entry[2])]
deleteList = utils.invertIntervalList(keepList, 0, tg.maxTimestamp)
intervals = tg.tierDict["phone"].entryList
deleteIntervals = [(entry[0], entry[1]) for entry in intervals if isVowel(entry[2])]
keepIntervals = utils.invertIntervalList(deleteIntervals, 0, wav.duration)

wavReader = wave.open(inputWavFN, "r")
replaceFunc = None
if doShrink is False:
generator = audio.AudioGenerator.fromWav(wav)
replaceFunc = generator.generateSilence

wavObj = audio.openAudioFile(inputWavFN, keepList=keepList, doShrink=doShrink)
wavObj.save(outputWavFN)
frames = audio.readFramesAtTimes(
wavReader, keepIntervals=keepIntervals, replaceFunc=replaceFunc
)
shrunkWav = audio.Wav(frames, wavReader.getparams())
shrunkWav.save(outputWavFN)

shrunkTG = copy.deepcopy(tg)
for start, end in sorted(deleteList, reverse=True):
for start, end in sorted(deleteIntervals, reverse=True):
shrunkTG = shrunkTG.eraseRegion(start, end, doShrink=doShrink)

shrunkTG.save(outputTGFN, "short_textgrid", True)
Expand Down
17 changes: 9 additions & 8 deletions examples/extract_subwavs.py
@@ -1,21 +1,22 @@
#!/usr/bin/env python
# encoding: utf-8
"""
Praatio example of extracting a separate wav file for each labeled entry in a textgrid tier
Created on Oct 21, 2021
@author: tmahrt
"""

import os
from os.path import join

from praatio import praatio_scripts
from praatio import audio

path = join(".", "files")
outputPath = join(path, "sub_wavs")

if not os.path.exists(outputPath):
os.mkdir(outputPath)

for wavFN, tgFN in [ # ("bobby.wav", "bobby_words.TextGrid"),
("mary.wav", "mary.TextGrid")
]:
praatio_scripts.splitAudioOnTier(
join(path, wavFN), join(path, tgFN), "phone", outputPath, True
)
inputFN = join(path, "mary.wav")
outputFN = join(outputPath, "mary_segment.wav")
audio.extractSubwav(inputFN, outputFN, 0.33, 0.89)
4 changes: 2 additions & 2 deletions examples/files/bobby_phones.TextGrid
Expand Up @@ -10,7 +10,7 @@ item []:
class = "IntervalTier"
name = "phone"
xmin = 0.0
xmax = 1.18979591837
xmax = 1.194625
intervals: size = 15
intervals [1]:
xmin = 0.0124716553288
Expand Down Expand Up @@ -70,5 +70,5 @@ item []:
text = "ER0"
intervals [15]:
xmin = 1.1171482864527198
xmax = 1.18979591837
xmax = 1.194625
text = ""
8 changes: 4 additions & 4 deletions examples/splice_example.py
Expand Up @@ -9,7 +9,7 @@
from praatio import audio
from praatio import praatio_scripts

root = r"C:\Users\Tim\Dropbox\workspace\praatIO\examples\files"
root = os.path.abspath(join(".", "files"))
audioFN = join(root, "mary.wav")
tgFN = join(root, "mary.TextGrid")

Expand All @@ -29,9 +29,9 @@
bEntry = tier.entryList[tier.find("b")[0]]


sourceAudioObj = audio.openAudioFile(audioFN)
mAudioObj = sourceAudioObj.getSubsegment(mEntry[0], mEntry[1])
bAudioObj = sourceAudioObj.getSubsegment(bEntry[0], bEntry[1])
sourceAudioObj = audio.Wav.open(audioFN)
mAudioObj = sourceAudioObj.getSubwav(mEntry[0], mEntry[1])
bAudioObj = sourceAudioObj.getSubwav(bEntry[0], bEntry[1])

# Replace 'm' with 'b'
audioObj, tg = praatio_scripts.audioSplice(
Expand Down
21 changes: 21 additions & 0 deletions examples/split_audio_on_tier.py
@@ -0,0 +1,21 @@
"""
Praatio example of extracting a separate wav file for each labeled entry in a textgrid tier
"""

import os
from os.path import join

from praatio import praatio_scripts

path = join(".", "files")
outputPath = join(path, "sub_wavs")

if not os.path.exists(outputPath):
os.mkdir(outputPath)

for wavFN, tgFN in [ # ("bobby.wav", "bobby_words.TextGrid"),
("mary.wav", "mary.TextGrid")
]:
praatio_scripts.splitAudioOnTier(
join(path, wavFN), join(path, tgFN), "phone", outputPath, True
)

0 comments on commit b1330dd

Please sign in to comment.