From 0b0901ec0462b9b3c5442a67a389703f40208b5b Mon Sep 17 00:00:00 2001 From: Mika Date: Sun, 19 Apr 2026 02:07:44 +0000 Subject: [PATCH] Add fft_analysis/src/fft_analysis/core.py --- fft_analysis/src/fft_analysis/core.py | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 fft_analysis/src/fft_analysis/core.py diff --git a/fft_analysis/src/fft_analysis/core.py b/fft_analysis/src/fft_analysis/core.py new file mode 100644 index 0000000..2708c91 --- /dev/null +++ b/fft_analysis/src/fft_analysis/core.py @@ -0,0 +1,58 @@ +"""Core module for performing FFT (Fast Fourier Transform) analysis on audio data. + +This module provides numerical processing of time-domain PCM audio signals using FFT, +returning their magnitude spectrum for further analysis and visualization. +""" + +from __future__ import annotations + +import numpy as np +from scipy import fft as sp_fft +from typing import Any + + +class InvalidAudioDataError(ValueError): + """Raised when provided audio_data is invalid for FFT computation.""" + + +def perform_fft(audio_data: np.ndarray) -> np.ndarray: + """Compute the frequency magnitude spectrum of the given audio data. + + Parameters + ---------- + audio_data : numpy.ndarray + 1D array with PCM audio data (time-domain). + + Returns + ------- + numpy.ndarray + Magnitude spectrum of the audio signal. + + Raises + ------ + InvalidAudioDataError + If the input array is invalid or empty. + """ + if not isinstance(audio_data, np.ndarray): + raise InvalidAudioDataError("audio_data must be a numpy.ndarray") + + if audio_data.ndim != 1: + raise InvalidAudioDataError("audio_data must be a 1D array") + + if audio_data.size == 0: + raise InvalidAudioDataError("audio_data is empty") + + # Normalize to range [-1, 1] to avoid scaling effects + max_val = np.max(np.abs(audio_data)) + if max_val == 0: + raise InvalidAudioDataError("audio_data contains only zeros") + + normalized = audio_data / max_val + + # Compute FFT using scipy for better numerical accuracy + spectrum_complex = sp_fft.fft(normalized) + magnitude = np.abs(spectrum_complex) + + # Return only the positive frequency components (real signal symmetry) + half_length = magnitude.shape[0] // 2 + return magnitude[:half_length]