---
title: Using Soundscapy for Binaural Recording Analysis
jupyter:
  jupytext:
    cell_markers: '"""'
  kernelspec:
    display_name: .venv
    language: python
    name: python3
setup-cells:
  python:
    language: "python"    # Language assumed for the setup cell
    code: |
      # Setup for Colab
      # Run this cell once to install the required packages
      # Then restart your session, and you can skip running this cell again
      !pip install "soundscapy[audio]>=0.8.4"
---


Soundscapy has evolved to provide a comprehensive suite of acoustic and psychoacoustic analyses. This tutorial will guide you through using the new `AcousticAnalysis` class, which serves as the primary interface for performing these analyses. The system is optimized for batch processing, ease of use, and reproducibility.

## Background

Soundscapy relies on three main packages to provide its analysis functions:

1. [Acoustic Toolbox](https://github.com/Universite-Gustave-Eiffel/acoustic-toolbox) (`acoustic_toolbox`): Provides standard acoustic metrics with direct references to relevant standards.
2. [scikit-maad](https://scikit-maad.github.io) (`maad`): Offers a suite of ecological soundscape and bioacoustic indices.
3. [MoSQITo](https://github.com/Eomys/MoSQITo) (`mosqito`): Provides key psychoacoustic metrics.

The metrics available include:
- From Acoustic Toolbox: $L_{Zeq}$, $L_{Aeq}$, $L_{Ceq}$, SEL, and associated statistics.
- From scikit-maad: Temporal and spectral alpha indices.
- From MoSQITo: Loudness, Sharpness, and Roughness.

Soundscapy combines all of these metrics and makes it easy and (relatively) fast to compute any or all of them for a binaural audio recording. These results have been preliminarily confirmed through comparison of results obtained from Head Acoustics ArtemiS suite on a set of real-world recordings.


## Getting Started

Let's begin by importing the necessary modules and setting up our environment:

```{python}
import json
from pathlib import Path

# imports
from soundscapy import AnalysisSettings, AudioAnalysis
```

Set up where the data is located. In this case, we'll use the sample recordings located under the `test` folder.

```{python}
# May need to adjust for your system
wav_folder = Path().cwd().parent.parent.joinpath("test", "data")
```

## Calibration Levels

Ensuring correct calibration is crucial for accurate analysis. If you used equipment such as the Head Acoustics SqoBold, and were careful about how the recordings are exported to .wav, then they may already be correctly adjusted (as ours are here). However its best to be safe and calibrate each signal to their real-world dB level. To do this, we load in a .json that contains the per-channel correct dB $L_{eq}$ level.

```{python}
levels_file = wav_folder.joinpath("Levels.json")

with levels_file.open("r", encoding="utf-8") as f:
    levels = json.load(f)

# Look at the first five sets of levels
list(levels.items())[:5]
```

## Initializing AudioAnalysis

The `AudioAnalysis` class is our main interface for performing acoustic analyses. Let's initialize it with default settings:

```{python}
analysis = AudioAnalysis()
```

By default, this loads the standard configuration. If you have a custom configuration file, you can specify it:

```{python}
# analysis = AudioAnalysis("path/to/custom_config.yaml")
```

## Analyzing a Single File

Let's analyze a single audio file:

```{python}
import time

start = time.perf_counter()

binaural_wav = wav_folder.joinpath("CT101.wav")
decibel = (levels["CT101"]["Left"], levels["CT101"]["Right"])

single_file_result = analysis.analyze_file(
    binaural_wav, calibration_levels=decibel, resample=48000
)
elapsed = time.perf_counter() - start
print(f"Elapsed: {elapsed:.2f} s")
single_file_result
```

This performs all the analyses specified in our configuration on the single file. The `calibration_levels` parameter ensures that the analysis is calibrated correctly.

## Batch Processing

Now, let's analyze all the WAV files in our folder:

```{python}
#| editable: true
#| slideshow: {slide_type: ''}
#| tags: [skip_execution]
import time

start = time.perf_counter()

folder_results = analysis.analyze_folder(
    wav_folder, calibration_file=levels_file, resample=48000
)

end = time.perf_counter()
print(f"Time taken: {end - start:.2f} seconds")
folder_results
```

### Saving Results

We can easily save our results to a file:

```{python}
#| tags: [skip-execution]
analysis.save_results(folder_results, "acoustic_analysis_results.xlsx")
```

## Customizing the Analysis

### Updating Configuration

If we want to modify our analysis configuration, we can do so using the `update_config` method:

```{python}
new_config = {"AcousticToolbox": {"LAeq": {"run": False}}}

analysis.update_config(new_config)
print("Configuration updated")
```

This would disable the LAeq analysis in subsequent runs.

### Saving the Updated Configuration

We can save the updated configuration to a file:

```{python}
analysis.save_config("updated_config.yaml")
print("Updated configuration saved to 'updated_config.yaml'")
```

Of course, you could also directly edit your config.yaml file instead.

## Advanced Usage

### Custom Analysis Settings

For more control, we can create a custom `AnalysisSettings`:

```{python}
custom_settings = AnalysisSettings.from_yaml("example_settings.yaml")
custom_settings.update_setting("scikit_maad", "all_temporal_alpha_indices", run=True)
custom_settings.update_setting("scikit_maad", "all_spectral_alpha_indices", run=True)

# Create a new AudioAnalysis instance with the custom settings
custom_analysis = AudioAnalysis(config_path="example_settings.yaml")

# Or update an existing instance
analysis.update_config(custom_settings.model_dump())
```

## Parallel Processing Control

The `analyze_folder` method uses parallel processing by default. You can control the number of worker processes using the `max_workers` argument. Setting `max_workers = None` (the default) will use all available CPU cores. Setting `max_workers = 1` will disable parallel processing, and will take significantly longer to process:

```{python}
#| tags: [skip-execution]
start = time.perf_counter()

serial_analysis = AudioAnalysis()
folder_results = serial_analysis.analyze_folder(
    wav_folder, calibration_file=levels_file, max_workers=1, resample=48000
)

end = time.perf_counter()

print(f"Time taken: {end - start:.2f} seconds")
folder_results
```

As we can see, on my system, enabling parallel processing reduces the processing time for these 8 files from almost 25 minutes to less than 4 minutes. This will vary depending on your system and the number of files you are processing. The more CPU cores and the more files, the more beneficial parallel processing will be.

## Conclusion

The `AudioAnalysis` class provides a powerful and flexible interface for performing acoustic and psychoacoustic analyses on binaural recordings. It simplifies the process of batch analysis, configuration management, and result handling, making it easier to process large datasets consistently and efficiently.

Remember that the specific metrics calculated and their settings are determined by the configuration. Always ensure your configuration accurately reflects your analysis needs, keep in mind that the psychoacoustic analyses in MoSQITo can be computationally intensive, and don't hesitate to customize it for your specific research or application requirements.
