Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Generated audio outputs
data/filtered.wav

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
28 changes: 0 additions & 28 deletions Audio.py

This file was deleted.

67 changes: 58 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
# Py_Audio
Project for interacting with audio files using python

## Major part Covered -
- Importing a wav audio file to python interface.
- Converting audio file to a numpy array.
- Reversing array by using python list functions.
- Packing reversed array back to wav file :)
A modular Python package for audio processing — read, transform, analyse, and
filter WAV files with a simple API built on NumPy and SciPy.

## Features

## New functions to be worked on -
- Using Audio Files with a Spectrum Analyzer tool
- Use BandPass filer to capture FFT frequency in wav Audio
| Module | Functions | Description |
|--------|-----------|-------------|
| `py_audio.io` | `read_audio`, `write_audio` | WAV file I/O with float64 conversion |
| `py_audio.effects` | `reverse_audio`, `change_speed`, `change_volume` | Time/amplitude effects |
| `py_audio.analysis` | `compute_fft`, `spectrum_analysis` | FFT spectrum analysis |
| `py_audio.filters` | `bandpass_filter` | Butterworth bandpass filter |

## Quick Start

```bash
pip install -r requirements.txt
```

```python
from py_audio import read_audio, write_audio, reverse_audio
from py_audio import spectrum_analysis, bandpass_filter

# Read a WAV file
sample_rate, audio = read_audio("data/Faded.wav")

# Reverse the audio
reversed_audio = reverse_audio(audio)
write_audio("data/rev_audio.wav", reversed_audio, sample_rate)

# Bandpass filter (200 Hz – 4000 Hz)
filtered = bandpass_filter(audio, sample_rate, low_freq=200, high_freq=4000)
write_audio("data/filtered.wav", filtered, sample_rate)

# Spectrum analysis
info = spectrum_analysis("data/Faded.wav")
print(f"Peak frequency: {info['peak_frequency']:.1f} Hz")
```

See `example.py` for a full runnable demo.

## Running Tests

```bash
pip install pytest
python -m pytest tests/ -v
```

## Project Structure

```
py_audio/ # Core package
__init__.py # Public API
io.py # WAV read/write
effects.py # Audio effects
analysis.py # FFT & spectrum analysis
filters.py # Digital filters
tests/ # Unit tests
example.py # Demo script
requirements.txt # Dependencies
```
Binary file modified data/rev_audio.wav
Binary file not shown.
46 changes: 46 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Example usage of the py_audio package.

Demonstrates reading a WAV file, applying effects and filters,
running spectrum analysis, and writing results back to disk.
"""

from py_audio import (
bandpass_filter,
read_audio,
reverse_audio,
spectrum_analysis,
write_audio,
)


def main() -> None:
source = "./data/Faded.wav"

# --- Read the original audio ---
sample_rate, audio = read_audio(source)
print(f"Loaded: {source}")
print(f" Sample rate : {sample_rate} Hz")
print(f" Samples : {audio.shape[0]}")
print(f" Duration : {audio.shape[0] / sample_rate:.2f} s")
print()

# --- Reverse the audio ---
reversed_audio = reverse_audio(audio)
write_audio("./data/rev_audio.wav", reversed_audio, sample_rate)
print("Saved reversed audio -> data/rev_audio.wav")

# --- Apply a bandpass filter (200 Hz – 4000 Hz) ---
filtered = bandpass_filter(audio, sample_rate, low_freq=200, high_freq=4000)
write_audio("./data/filtered.wav", filtered, sample_rate)
print("Saved filtered audio -> data/filtered.wav")

# --- Spectrum analysis ---
info = spectrum_analysis(source)
print()
print("Spectrum analysis:")
print(f" Peak frequency : {info['peak_frequency']:.1f} Hz")
print(f" Duration : {info['duration']:.2f} s")


if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions py_audio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""py_audio – A modular Python package for audio processing.

Provides WAV I/O, effects, spectrum analysis, and digital filtering
built on NumPy and SciPy.

Quick start::

from py_audio import read_audio, write_audio, reverse_audio
from py_audio import spectrum_analysis, bandpass_filter
"""

from py_audio.analysis import compute_fft, spectrum_analysis
from py_audio.effects import change_speed, change_volume, reverse_audio
from py_audio.filters import bandpass_filter
from py_audio.io import read_audio, write_audio

__all__ = [
"read_audio",
"write_audio",
"reverse_audio",
"change_speed",
"change_volume",
"compute_fft",
"spectrum_analysis",
"bandpass_filter",
]
82 changes: 82 additions & 0 deletions py_audio/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Spectrum analysis utilities for audio signals."""

from typing import Any, Dict, Tuple

import numpy as np
from numpy.typing import NDArray

from py_audio.io import read_audio


def compute_fft(
data: NDArray[np.float64],
sample_rate: int,
) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
"""Compute the single-sided FFT magnitude spectrum of an audio signal.

For stereo (2-D) input the channels are averaged to mono before the
FFT is computed.

Args:
data: Audio samples (1-D mono or 2-D stereo).
sample_rate: Sample rate in Hz.

Returns:
A tuple ``(frequencies, magnitudes)`` where both are 1-D numpy
arrays covering frequencies from 0 Hz up to the Nyquist frequency.

Raises:
ValueError: If *data* is empty.
"""
if data.size == 0:
raise ValueError("Cannot compute FFT of an empty array.")

# Mix to mono if stereo.
if data.ndim == 2:
data = data.mean(axis=1)

n = len(data)
fft_vals = np.fft.rfft(data)
magnitudes = np.abs(fft_vals) * (2.0 / n)
frequencies = np.fft.rfftfreq(n, d=1.0 / sample_rate)

return frequencies, magnitudes


def spectrum_analysis(path: str) -> Dict[str, Any]:
"""Read a WAV file and return its spectral characteristics.

Args:
path: Path to a WAV file.

Returns:
A dictionary with the following keys:

* ``frequencies`` – 1-D array of FFT bin frequencies (Hz).
* ``magnitudes`` – 1-D array of magnitude values.
* ``sample_rate`` – Sample rate of the file (Hz).
* ``duration`` – Duration of the audio in seconds.
* ``peak_frequency`` – Frequency (Hz) with the highest magnitude,
excluding the DC component at index 0.
"""
sample_rate, data = read_audio(path)

num_samples = data.shape[0]
duration = num_samples / sample_rate

frequencies, magnitudes = compute_fft(data, sample_rate)

# Find the peak frequency, excluding DC (index 0).
if len(magnitudes) > 1:
peak_index = np.argmax(magnitudes[1:]) + 1
peak_frequency = float(frequencies[peak_index])
else:
peak_frequency = 0.0

return {
"frequencies": frequencies,
"magnitudes": magnitudes,
"sample_rate": sample_rate,
"duration": duration,
"peak_frequency": peak_frequency,
}
72 changes: 72 additions & 0 deletions py_audio/effects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Audio effects: reverse, speed change, and volume adjustment."""

import numpy as np
from numpy.typing import NDArray
from scipy.signal import resample


def reverse_audio(data: NDArray[np.float64]) -> NDArray[np.float64]:
"""Reverse an audio array along the time axis.

Works for both mono (1-D) and stereo (2-D, shape ``(samples, channels)``)
arrays.

Args:
data: Audio samples as a numpy array.

Returns:
A new array with samples in reverse order.
"""
return np.flip(data, axis=0)


def change_speed(
data: NDArray[np.float64],
factor: float,
) -> NDArray[np.float64]:
"""Change playback speed by resampling.

A *factor* greater than 1 speeds up the audio (fewer output samples);
a *factor* less than 1 slows it down (more output samples).

.. note::
Very large factors may reduce the audio to just a few samples,
which is unlikely to be musically useful.

Args:
data: Audio samples as a numpy array (1-D or 2-D).
factor: Speed multiplier. Must be positive.

Returns:
Resampled audio array.

Raises:
ValueError: If *factor* is not positive.
"""
if factor <= 0:
raise ValueError("Speed factor must be positive.")

num_samples = data.shape[0]
new_length = int(round(num_samples / factor))
if new_length == 0:
new_length = 1

return resample(data, new_length, axis=0)


def change_volume(
data: NDArray[np.float64],
gain_db: float,
) -> NDArray[np.float64]:
"""Apply a gain adjustment in decibels.

Args:
data: Audio samples as a numpy array.
gain_db: Gain to apply in dB. Positive values amplify,
negative values attenuate.

Returns:
Gain-adjusted audio array.
"""
multiplier = 10.0 ** (gain_db / 20.0)
return data * multiplier
50 changes: 50 additions & 0 deletions py_audio/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Digital audio filters built on scipy's signal processing toolkit."""

import numpy as np
from numpy.typing import NDArray
from scipy.signal import butter, sosfilt


def bandpass_filter(
data: NDArray[np.float64],
sample_rate: int,
low_freq: float,
high_freq: float,
order: int = 5,
) -> NDArray[np.float64]:
"""Apply a Butterworth bandpass filter to audio data.

Uses second-order sections (``sos``) for numerical stability.
Works with both mono (1-D) and stereo (2-D) arrays. For stereo
input each channel is filtered independently.

Args:
data: Audio samples as a numpy array.
sample_rate: Sample rate in Hz.
low_freq: Lower cutoff frequency in Hz.
high_freq: Upper cutoff frequency in Hz.
order: Filter order (default 5).

Returns:
Filtered audio as a numpy array with the same shape as *data*.

Raises:
ValueError: If the frequency parameters are invalid.
"""
nyquist = sample_rate / 2.0

if low_freq <= 0 or high_freq <= 0:
raise ValueError("Cutoff frequencies must be positive.")
if low_freq >= high_freq:
raise ValueError("low_freq must be less than high_freq.")
if high_freq >= nyquist:
raise ValueError(
f"high_freq ({high_freq} Hz) must be below the Nyquist "
f"frequency ({nyquist} Hz)."
)

low = low_freq / nyquist
high = high_freq / nyquist

sos = butter(order, [low, high], btype="band", output="sos")
return sosfilt(sos, data, axis=0)
Loading