Skip to content
Merged
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
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Streamlit App CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

- name: Test Streamlit Config
run: |
streamlit config show
15 changes: 15 additions & 0 deletions .streamlit/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[theme]
base = "dark"
primaryColor = "#6366f1"
# Removing hardcoded backgrounds to allow native toggling
# backgroundColor = "#0e1117"
# secondaryBackgroundColor = "#1e1e2e"
# textColor = "#fafafa"
font = "sans serif"

[server]
maxUploadSize = 50
enableXsrfProtection = true

[browser]
gatherUsageStats = false
90 changes: 65 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,82 @@
# Python Sound Wave Analysis
# 🌊 Sound Wave Analysis

A simple open-source project for analyzing and visualizing sound waves using Python. Clean, lightweight, and beginner-friendly.
A professional, web-based tool for analyzing and visualizing audio files. Built with **Streamlit** and **Plotly**, this application provides physics-grade analysis of sound waves, supporting WAV, MP3, and FLAC formats.

---
![App Screenshot](https://raw.githubusercontent.com/TorresjDev/Python-Sound-Wave-Analysis/main/assets/app_preview.png)
*(Note: Replace with actual screenshot path once pushed)*

## License
This project is licensed under the **Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)** license.
You may use, share, and modify the project **with attribution**, but **not for commercial purposes**.
## 🚀 Features

[![DOI](https://zenodo.org/badge/1096327265.svg)](https://doi.org/10.5281/zenodo.17607793)
### 📊 Professional Visualization
- **Waveform**: Interactive time-domain display.
- **Frequency Spectrum**: Audacity-style spectrum analysis with log scale frequency and dB.
- **Spectrogram**: Time-frequency intensity heatmap.
- **Power Spectral Density (PSD)**: Energy distribution across frequencies.
- **Phase Response**: Phase angle vs. frequency.
- **Amplitude Histogram**: Distribution of signal amplitudes.

Full license text is available in the `LICENSE` file.
### 🔬 Detailed Analysis
- **Audio Metrics**: Sample rate, duration, channels, RMS dB, dynamic range.
- **Harmonic Detection**: Identifies fundamental frequency and up to 5 overtones.
- **Speed of Sound Calculator**: Real-time calculator for various media (Air, Water, Steel, etc.) with temperature adjustment.

---
### 🛠️ Key Capabilities
- **Multi-Format Support**: Upload WAV, MP3, or FLAC files (auto-converted).
- **Audio Playback**: Listen to your audio directly in the browser.
- **Interactive UI**: Native Dark/Light mode support (toggles via Streamlit Settings).
- **Export Options**: Download analysis data as CSV or a text summary.

## Getting Started
## 🛠️ Tech Stack

Clone the repository:
- **Frontend**: [Streamlit](https://streamlit.io/)
- **Visualization**: [Plotly](https://plotly.com/python/)
- **Audio Processing**: [NumPy](https://numpy.org/), [SciPy](https://scipy.org/), [Pydub](https://github.com/jiaaro/pydub)
- **Deployment**: Streamlit Cloud

```bash
git clone https://github.com/TorresjDev/Python-Sound-Wave-Analysis.git
````
## 📦 Installation & Local Development

Install dependencies:
1. **Clone the repository:**
```bash
git clone https://github.com/TorresjDev/Python-Sound-Wave-Analysis.git
cd Python-Sound-Wave-Analysis
```

```bash
pip install -r requirements.txt
```
2. **Install dependencies:**
```bash
pip install -r requirements.txt
```
*Note: For MP3/FLAC support, ensure you have [ffmpeg](https://ffmpeg.org/) installed on your system.*

Run the program:
3. **Run the app:**
```bash
streamlit run streamlit_app.py
```

```bash
python main.py
```
4. **Open in browser:**
The app will automatically open at `http://localhost:8501`.

---
## ☁️ Deployment

### Deploying to Streamlit Cloud

1. Push your code to GitHub.
2. Sign in to [Streamlit Cloud](https://share.streamlit.io/).
3. Click **"New App"**.
4. Select your repository (`TorresjDev/Python-Sound-Wave-Analysis`), branch (`main`), and main file (`streamlit_app.py`).
5. Click **"Deploy"**.

## Author
Streamlit Cloud will automatically detect `packages.txt` (if added for ffmpeg) and `requirements.txt` to install dependencies.

Created by **Jesus Torres (TorresjDev)**
## 🧪 CI/CD

This project uses **GitHub Actions** for continuous integration:
- **Python Linting**: Checks for syntax errors and coding standards.
- **Dependency Test**: Verifies that `requirements.txt` installs correctly.
- **Streamlit Config Check**: Ensures the app configuration is valid.

## 📜 License

This project is licensed under the CC BY-NC 4.0 License.

---
**Created by [TorresjDev](https://github.com/TorresjDev)**
Binary file added data/space_odyssey_radar.wav
Binary file not shown.
Binary file added figures/frequency_spectrum_audacity_style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added figures/space_odyssey_radar_spectrogram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added figures/space_odyssey_radar_waveform.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""
Sound Wave Analysis - Clean and Modular

A user-friendly tool for analyzing WAV files with visualization.

Author: TorresjDev
License: MIT
"""

import os
from sound_analysis.analyzer import perform_complete_analysis
from sound_analysis.tools import select_wav_file, get_analysis_options


def main():
"""Main function - clean and modular."""
print("🌊 Welcome to Sound Wave Analysis!")
print("=" * 40)

# Let user select a WAV file
selected_file = select_wav_file()

if selected_file:
print(f"\n🎯 Analyzing: {os.path.basename(selected_file)}")

# Get analysis options
options = get_analysis_options()

# Perform analysis using the analyzer module
perform_complete_analysis(
selected_file,
show_plots=options["show_plots"],
save_figures=options["save_figures"]
)
else:
print("\n👋 Goodbye!")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions packages.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffmpeg
19 changes: 19 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Core dependencies for Sound Wave Analysis
numpy>=1.21.0
matplotlib>=3.5.0
scipy>=1.7.0

# Enhanced user interface (CLI)
keyboard>=0.13.5

# Streamlit Web App
streamlit>=1.29.0
plotly>=5.18.0

# Audio format support (MP3, FLAC)
pydub>=0.25.1

# Development dependencies (optional)
# pytest>=6.0.0
# black>=21.0.0
# flake8>=3.9.0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added sound_analysis/__pycache__/tools.cpython-312.pyc
Binary file not shown.
Binary file not shown.
162 changes: 162 additions & 0 deletions sound_analysis/analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""
Sound Analysis Analyzer

Core analysis functions for processing WAV files.
"""

import os
import wave
import numpy as np
from .tools import wave_to_db, wave_to_db_rms, detect_db_range, list_wav_files
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'list_wav_files' is not used.

Suggested change
from .tools import wave_to_db, wave_to_db_rms, detect_db_range, list_wav_files
from .tools import wave_to_db, wave_to_db_rms, detect_db_range

Copilot uses AI. Check for mistakes.
from .visualization import plot_waveform, plot_spectrogram, plot_combined_analysis
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'plot_combined_analysis' is not used.

Suggested change
from .visualization import plot_waveform, plot_spectrogram, plot_combined_analysis
from .visualization import plot_waveform, plot_spectrogram

Copilot uses AI. Check for mistakes.


def get_wave_info(file_path):
"""Get basic information about a WAV file."""
try:
wav_obj = wave.open(file_path, "rb")

info = {
'sample_rate': wav_obj.getframerate(),
'total_samples': wav_obj.getnframes(),
'channels': wav_obj.getnchannels(),
'sample_width': wav_obj.getsampwidth(),
}

info['duration'] = info['total_samples'] / info['sample_rate']
info['channel_type'] = "Mono" if info['channels'] == 1 else "Stereo"

wav_obj.close()
return info

except Exception as e:
raise Exception(f"Error reading WAV file info: {str(e)}")


def load_wave_data(file_path):
"""Load waveform data from a WAV file."""
try:
wav_obj = wave.open(file_path, "rb")

# Get file info
sample_rate = wav_obj.getframerate()
total_samples = wav_obj.getnframes()
channels = wav_obj.getnchannels()

# Read audio data
raw_data = wav_obj.readframes(total_samples)
audio_data = np.frombuffer(raw_data, dtype=np.int16)

# Handle mono/stereo
if channels == 1:
waveform = audio_data
else:
waveform = audio_data[0::2] # Use left channel for stereo

wav_obj.close()

return {
'waveform': waveform,
'sample_rate': sample_rate,
'duration': total_samples / sample_rate,
'channels': channels
}

except Exception as e:
raise Exception(f"Error loading WAV data: {str(e)}")


def analyze_audio_levels(waveform):
"""Analyze various audio level metrics."""
# Basic statistics
max_amplitude = np.max(np.abs(waveform))
min_amplitude = np.min(np.abs(waveform))
mean_amplitude = np.mean(np.abs(waveform))

# Decibel calculations
avg_db = wave_to_db(waveform)
rms_db = wave_to_db_rms(waveform)
db_range = detect_db_range(waveform)

return {
'max_amplitude': max_amplitude,
'min_amplitude': min_amplitude,
'mean_amplitude': mean_amplitude,
'avg_db': avg_db,
'rms_db': rms_db,
'db_range': db_range
}


def perform_complete_analysis(file_path, show_plots=True, save_figures=False):
"""Perform complete analysis of a WAV file."""
try:
# Get file info
file_info = get_wave_info(file_path)

# Load waveform data
wave_data = load_wave_data(file_path)
waveform = wave_data['waveform']
sample_rate = wave_data['sample_rate']
duration = wave_data['duration']

# Analyze audio levels
audio_levels = analyze_audio_levels(waveform)

# Create filename for plots
filename = os.path.basename(file_path)

# Display results
print("\n🎵 Analysis Results")
print("=" * 40)
print(f"📁 File: {filename}")
print(f"📊 Sample Rate: {file_info['sample_rate']:,} Hz")
print(f"⏱️ Duration: {duration:.2f} seconds")
print(
f"🎧 Channels: {file_info['channels']} ({file_info['channel_type']})")
print(f"📈 Total Samples: {file_info['total_samples']:,}")

print(f"\n📈 Sound Levels:")
print(f"🔊 Average dB: {audio_levels['avg_db']:.2f}")
print(f"📊 RMS dB: {audio_levels['rms_db']:.2f}")
print(
f"📏 Dynamic Range: {audio_levels['db_range']['dynamic_range']:.2f} dB")
print(f"📈 Max dB: {audio_levels['db_range']['max_db']:.2f}")
print(f"📉 Min dB: {audio_levels['db_range']['min_db']:.2f}")

# Generate visualizations
if show_plots:
print("\n🎨 Generating visualizations...")

if save_figures:
figures_dir = "figures"
os.makedirs(figures_dir, exist_ok=True)
base_name = os.path.splitext(filename)[0]

waveform_path = os.path.join(
figures_dir, f"{base_name}_waveform.png")
spectrogram_path = os.path.join(
figures_dir, f"{base_name}_spectrogram.png")
else:
waveform_path = None
spectrogram_path = None

plot_waveform(waveform, sample_rate, duration,
f"{filename} - Waveform", waveform_path)
plot_spectrogram(waveform, sample_rate,
f"{filename} - Spectrogram", spectrogram_path)

# Optional: Combined analysis plot
# plot_combined_analysis(waveform, sample_rate, duration, filename)

print("\n✅ Analysis completed!")

return {
'file_info': file_info,
'wave_data': wave_data,
'audio_levels': audio_levels
}

except Exception as e:
print(f"❌ Error analyzing file: {str(e)}")
return None
Loading