Skip to content

Commit

Permalink
Added audio functions with docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mbsantiago committed Jul 9, 2023
1 parent dc4033a commit 4be3e12
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 28 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ jobs:
path: .cache
restore-keys: |
mkdocs-material-
- run: pip install mkdocs-material "mkdocstrings[python]"
- run:
pip install mkdocs-material "mkdocstrings[python]" mkdocs-gallery memory_profiler
- run: mkdocs gh-deploy --force
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,6 @@ dmypy.json

# Cython debug symbols
cython_debug/

# Generated docs
docs/generated/
3 changes: 3 additions & 0 deletions docs/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Examples

Here are a list of examples
85 changes: 85 additions & 0 deletions docs/examples/loading_audio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""
# Loading audio
One of the fundamental operations in computational bioacoustics is **reading**
audio files into a workable format. In `soundevent`, we use
[`xarray.DataArray`][xarray.DataArray] objects to hold loaded audio data.
[`xarray.DataArray`][xarray.DataArray] objects are an extension of
[`numpy`][numpy.ndarray] arrays, so there's no need to learn new concepts
if you are already familiar with [`numpy`][numpy.ndarray] arrays.
!!! note "Why use `xarray.DataArray` objects?"
`xarray.DataArray` objects offer two key benefits: coordinates for easier
referencing of time-related locations in the array, and the ability to
store additional metadata such as `samplerate`, `time_expansion`, and
specify that the temporal units are seconds. To learn more about
`xarray.DataArray` objects, see the
[xarray documentation](https://docs.xarray.dev/en/stable/getting-started-guide/why-xarray.html).
"""

# %%
# ## Getting a Recording object
# To create a [`data.Recording`][soundevent.data.Recording] object from an
# audio file, you can use the
# [`from_file`][soundevent.data.Recording.from_file] method. This method
# extracts the metadata from the file and populates the `Recording` object with
# the relevant information.

from soundevent import data

recording = data.Recording.from_file("sample_audio.wav")
print(repr(recording))

# %%
# ## Loading the audio
# Once you have a [`data.Recording`][soundevent.data.Recording] object, you can
# load the audio data using the
# [`audio.load_recording`][soundevent.audio.load_recording] function:

from soundevent import audio

wav = audio.load_recording(recording)
print(wav)

# %%
# Note that the returned object is an [`xarray.DataArray`][xarray.DataArray]
# object with two dimensions: time and channel. The time coordinate represents
# the array of times in seconds corresponding to the samples in the
# xarray.DataArray object.

# %%
# ## Selecting clips from a recording
# You can use the [`sel`][xarray.DataArray.sel] method of xarray.DataArray to
# select a clip from the recording. This is useful when you have the full file
# loaded into memory and want to extract a specific clip:

# You can select a clip by specifying the start and end times in seconds.
subwav = wav.sel(time=slice(0, 1))
print(repr(subwav))

# %%
# Alternatively, if you only need to load a clip from the file without loading
# the entire file into memory, you can use the
# [`audio.load_clip`][soundevent.audio.load_clip] function:

clip = data.Clip(
recording=recording,
start_time=0,
end_time=1,
)
subwav2 = audio.load_clip(clip)
print(repr(subwav2))

# %%
# In most cases, the results from `wav.sel` and `audio.load_clip` will be the
# same, except for the last sample. However, the difference is negligible, and
# the [`audio.load_clip`][soundevent.audio.load_clip] function is generally
# preferred for efficiency.
#
# You can verify the similarity of the clips using numpy.allclose:

import numpy as np
print(np.allclose(subwav[:-1], subwav2))
Binary file added docs/examples/sample_audio.wav
Binary file not shown.
5 changes: 5 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Reference

::: soundevent.data

::: soundevent.audio
34 changes: 32 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ site_url: https://mbsantiago.github.io/soundevent/
nav:
- Home: README.md
- Data: data.md
- generated/gallery
- Reference: reference.md
- Contributing: CONTRIBUTING.md
- Code of Conduct: CODE_OF_CONDUCT.md

Expand All @@ -13,10 +15,36 @@ theme:
- content.code.copy

plugins:
- search
- gallery:
examples_dirs: docs/examples # path to your example scripts
gallery_dirs: docs/generated/gallery # where to save generated gallery
backreferences_dir: docs/generated/backreferences # where to generate the back references summary
doc_module: ["mkdocs_gallery", "numpy"]
image_scrapers: matplotlib
compress_images: ["images", "thumbnails"]
within_subsection_order: FileNameSortKey
filename_pattern: ""
# binder:
# org: mbsantiago
# repo: soundevent
# branch: gh-pages
# # binderhub_url: https://mybinder.org
# dependencies: docs/binder_cfg/requirements.txt # binder configuration files
# # see https://mybinder.readthedocs.io/en/latest/using/config_files.html#config-files
# # these will be copied to the .binder/ directory of the site.
# notebooks_dir: notebooks
# use_jupyter_lab: True
show_memory: True
capture_repr: ['_repr_html_', '__repr__']
matplotlib_animations: True
image_srcset: ["2x"]

- mkdocstrings:
handlers:
python:
import:
- https://docs.xarray.dev/en/stable/objects.inv
- https://numpy.org/doc/stable/objects.inv
options:
show_root_heading: true
show_source: false
Expand All @@ -28,6 +56,8 @@ plugins:
ignore_init_summary: false
merge_init_into_class: true

- search

watch:
- docs
- src
Expand All @@ -43,6 +73,6 @@ markdown_extensions:
pygments_lang_class: true
- pymdownx.inlinehilite
- toc:
toc_depth: 3
toc_depth: 4
permalink: "#"
separator: "_"
64 changes: 63 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ dev = [
"mkdocstrings[python]>=0.22.0",
"tox>=4.6.3",
"hypothesis>=6.79.4",
"mkdocs-gallery>=0.7.8",
"memory-profiler>=0.61.0",
]

[project]
Expand Down
12 changes: 12 additions & 0 deletions src/soundevent/audio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Soundevent functions for handling audio files and arrays."""

from .io import load_clip, load_recording
from .media_info import get_media_info
from .spectrograms import generate_spectrogram

__all__ = [
"load_clip",
"load_recording",
"generate_spectrogram",
"get_media_info",
]
16 changes: 8 additions & 8 deletions src/soundevent/audio/chunks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
"""
import os
from dataclasses import dataclass, field
from typing import BinaryIO, List, Optional, Union

PathLike = Union[os.PathLike, str]

from typing import BinaryIO, Dict, List, Optional

CHUNKS_WITH_SUBCHUNKS = ["RIFF", "LIST"]

Expand All @@ -40,15 +37,15 @@ class Chunk:
The position of the chunk in the file.
chunk_size : int
The chunk size.
subchunks : List[Chunk]
The subchunks of the chunk.
subchunks : Dict[Chunk]
A dictionary holding the subchunks of the chunk.
"""

chunk_id: str
size: int
position: int
identifier: Optional[str] = None
subchunks: List["Chunk"] = field(default_factory=list)
subchunks: Dict[str, "Chunk"] = field(default_factory=dict)


def _get_subchunks(riff: BinaryIO, size: int) -> List[Chunk]:
Expand Down Expand Up @@ -106,7 +103,10 @@ def _read_chunk(riff: BinaryIO) -> Chunk:
)

if chunk_id in CHUNKS_WITH_SUBCHUNKS:
chunk.subchunks = _get_subchunks(riff, size - 4)
chunk.subchunks = {
subchunk.chunk_id: subchunk
for subchunk in _get_subchunks(riff, size - 4)
}
else:
riff.seek(size, os.SEEK_CUR)

Expand Down
7 changes: 4 additions & 3 deletions src/soundevent/audio/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import xarray as xr
from scipy.io import wavfile

from soundevent import data
from soundevent.data.clips import Clip
from soundevent.data.recordings import Recording

__all__ = [
"load_audio",
Expand Down Expand Up @@ -67,7 +68,7 @@ def load_audio(
return data, samplerate


def load_recording(recording: data.Recording) -> xr.DataArray:
def load_recording(recording: Recording) -> xr.DataArray:
"""Load a recording from a file.
Parameters
Expand Down Expand Up @@ -102,7 +103,7 @@ def load_recording(recording: data.Recording) -> xr.DataArray:
)


def load_clip(clip: data.Clip) -> xr.DataArray:
def load_clip(clip: Clip) -> xr.DataArray:
"""Load a clip from a file.
Parameters
Expand Down
Loading

0 comments on commit 4be3e12

Please sign in to comment.