Metadata-Version: 2.4
Name: gw-sonification
Version: 0.1.0
Summary: Audio sonifications of gravitational wave events
Author-email: Daniel Williams <mail@daniel-williams.co.uk>
License: MIT
Project-URL: Homepage, https://github.com/transientlunatic/sonification
Project-URL: Issues, https://github.com/transientlunatic/sonification/issues
Keywords: gravitational waves,sonification,LIGO,audio,outreach
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Astronomy
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.0
Requires-Dist: gwpy>=3.0
Requires-Dist: gwosc>=0.7
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: librosa>=0.10
Provides-Extra: waveform
Requires-Dist: pycbc; extra == "waveform"
Provides-Extra: gwresults
Requires-Dist: gwresults>=0.1; extra == "gwresults"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: build; extra == "dev"
Dynamic: license-file

# gw-sonification

Audio sonifications of gravitational-wave events from the LIGO/Virgo/KAGRA
detectors.  Strain data is downloaded from [GWOSC](https://gwosc.org), whitened,
bandpassed, and written as a WAV file.  A matching waveform template
(generated via PyCBC) can be produced alongside it.

## Installation

```bash
pip install -e .                       # core (strain data only)
pip install -e ".[waveform]"           # + pycbc for waveform templates
pip install -e ".[waveform,dev]"       # + dev tools (pytest, ruff)
```

The package exposes a `sonify` command-line tool and a Python API.

## Command-line usage

### Sonify a single event

```bash
# Sonified strain + waveform template, into the current directory
sonify event GW150914

# All standard variants (base + pitch-shifted by one octave)
sonify event GW150914 --all-variants

# Specific detector, custom pitch shift, custom output directory
sonify event GW190521_030229 --detector L1 --pitch-shift 7 --output-dir ./audio

# Skip generating the waveform template
sonify event GW150914 --all-variants --no-waveform

# Skip files that already exist
sonify event GW150914 --all-variants --skip-existing

# Use a local catalog JSON for event parameters (needed for accurate waveforms)
sonify event GW150914 --catalog gwtc4-all.json --all-variants
```

### Batch-process a catalog

```bash
# Process all events in a local catalog JSON, skipping existing files
sonify batch --catalog gwtc4-all.json --output-dir ./audio

# Process only specific events
sonify batch --catalog gwtc4-all.json -e GW150914 -e GW170817

# Use the live GWOSC API as the catalog source
sonify batch --catalog gwosc --output-dir ./audio --no-waveform

# No pitch-shift variants
sonify batch --catalog gwtc4-all.json --no-all-variants
```

### Check progress

```bash
sonify status ./audio              # summary count
sonify status ./audio --show-complete   # list complete events too
```

## Output file naming

All output files follow a consistent convention:

```
{event_name}-{detector}-{type}[-{effect}].wav
```

| Component    | Values                                |
|--------------|---------------------------------------|
| `event_name` | GWOSC name, e.g. `GW150914`           |
| `detector`   | `H1`, `L1`, or `V1`                   |
| `type`       | `sonified` or `waveform`              |
| `effect`     | `transpose`, `stretched`, … (optional)|

Examples:

```
GW150914-H1-sonified.wav
GW150914-H1-sonified-transpose.wav
GW150914-H1-waveform.wav
GW150914-H1-waveform-transpose.wav
```

## Python API

```python
from sonification import Event, Waveform
from sonification.effects import PitchShift, TimeStretch

# Strain data
ev = Event("GW150914")
ev.sonify(output_dir="./audio", effects=[PitchShift(12)])

# Force a specific detector
ev = Event("GW170817", detector="L1")
ev.sonify("./audio")

# Waveform template from a catalog
from sonification.catalog import JSONCatalog
cat = JSONCatalog("gwtc4-all.json")
params = cat.get_event("GW150914")

wf = Waveform.from_event(params)
wf.sonify("GW150914", "H1", output_dir="./audio", effects=[PitchShift(12)])

# Custom waveform parameters
wf = Waveform({"mass1": 36.0, "mass2": 29.0, "spin1z": 0.3})
wf.sonify("GW150914", "H1", "./audio")
```

## Extending the package

### Custom catalog providers

Implement `CatalogProvider` to plug in a new data source:

```python
from sonification.catalog import CatalogProvider, EventParameters

class MyCatalog(CatalogProvider):
    def get_event(self, name: str) -> EventParameters:
        # fetch from your database / API / file
        return EventParameters(name=name, gps=..., detectors=["H1"])

    def list_events(self) -> list[str]:
        return [...]

# Use it anywhere a catalog is accepted
ev = Event("GW150914", catalog=MyCatalog())
```

### Custom audio effects

Implement `AudioEffect` to add new post-processing steps:

```python
import numpy as np
from sonification.effects import AudioEffect

class Reverse(AudioEffect):
    """Play the audio backwards."""

    @property
    def name_suffix(self) -> str:
        return "reversed"

    def apply(self, data: np.ndarray, sample_rate: int) -> np.ndarray:
        return data[::-1].copy()

# Pass to sonify()
ev = Event("GW150914")
ev.sonify("./audio", effects=[Reverse()])
# writes: GW150914-H1-sonified.wav
#         GW150914-H1-sonified-reversed.wav
```

Multiple effects can be chained — each produces its own output file:

```python
from sonification.effects import PitchShift, TimeStretch

ev.sonify("./audio", effects=[PitchShift(12), TimeStretch(0.5)])
# writes: GW150914-H1-sonified.wav
#         GW150914-H1-sonified-transpose.wav
#         GW150914-H1-sonified-stretched.wav
```

## Processing pipeline

For strain data (`Event`):

1. Download 36 s of open strain data from GWOSC (cached locally).
2. Detrend and apply a Tukey taper.
3. Whiten using a 4 s FFT window with 2 s overlap.
4. Bandpass filter: 30–400 Hz.
5. Crop to a 32 s window centred 2 s after merger.
6. Normalise to float32 in [−1, 1] and write as 16-bit PCM WAV.
7. Apply each requested audio effect and write additional files.

For waveform templates (`Waveform`):

1. Generate a time-domain `IMRPhenomXPHM` waveform via PyCBC.
2. Extract a 4 s window around the merger.
3. Normalise and write as above.

## Data

Strain data is downloaded from [GWOSC](https://gwosc.org) and cached by gwpy.
Waveform templates use the `IMRPhenomXPHM` approximant from
[PyCBC](https://pycbc.org).
