### Calculate Spectrogram with STFT Source: https://github.com/liamappelbe/fftea/blob/main/README.md This snippet demonstrates how to compute a spectrogram using the Short-Time Fourier Transform (STFT). It utilizes a windowing function (Hanning in this example) and processes audio data in chunks. The result can be further processed to discard conjugate data and extract magnitudes for visualization. ```dart import 'package:fftea/fftea.dart'; List audio = ...; final chunkSize = 1234; final stft = STFT(chunkSize, Window.hanning(chunkSize)); final spectrogram = []; stft.run(audio, (Float64x2List freq) { spectrogram.add(freq.discardConjugates().magnitudes()); }); ``` -------------------------------- ### Get Frequency from Spectrogram Index Source: https://github.com/liamappelbe/fftea/blob/main/README.md Use this utility to determine the actual frequency represented by an index in the spectrogram. Requires the index and the original sampling frequency of the audio data. ```dart stft.frequency(index, samplingFrequency) ``` -------------------------------- ### Get Nyquist Frequency Source: https://github.com/liamappelbe/fftea/blob/main/README.md Calculates the maximum frequency (Nyquist frequency) representable in the spectrogram. This is determined by the chunk size used in the STFT and the audio's sampling frequency. ```dart stft.frequency(chunkSize / 2, samplingFrequency) ``` -------------------------------- ### Generate Spectrograms with STFT Source: https://context7.com/liamappelbe/fftea/llms.txt Shows how to process audio into overlapping chunks using a window function and compute frequency data for spectrograms. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { // Generate test audio: chirp signal (frequency increases over time) const sampleRate = 44100; const duration = 1.0; // seconds final audio = List.generate( (sampleRate * duration).toInt(), (i) { double t = i / sampleRate; double freq = 100 + 2000 * t; // Sweep from 100 Hz to 2100 Hz return sin(2 * pi * freq * t); }, ); // Create STFT with Hanning window const chunkSize = 2048; final stft = STFT(chunkSize, Window.hanning(chunkSize)); // Collect spectrogram data final spectrogram = []; stft.run( audio, (Float64x2List chunk) { // Discard conjugates (redundant) and keep only magnitudes spectrogram.add(chunk.discardConjugates().magnitudes()); }, chunkSize ~/ 2, // 50% overlap between chunks ); print('Generated ${spectrogram.length} time frames'); print('Each frame has ${spectrogram[0].length} frequency bins'); // Get frequency of a specific bin double freqAt100 = stft.frequency(100, sampleRate.toDouble()); print('Bin 100 corresponds to ${freqAt100.toStringAsFixed(1)} Hz'); // Find bin for a specific frequency double binFor1000Hz = stft.indexOfFrequency(1000.0, sampleRate.toDouble()); print('1000 Hz is at bin ${binFor1000Hz.round()}'); } ``` -------------------------------- ### Radix2FFT Performance for Power-of-Two Sizes Source: https://context7.com/liamappelbe/fftea/llms.txt Demonstrates how Radix2FFT is automatically used for power-of-two sizes and shows performance benchmarks. For optimal performance, zero-pad signals to the next power of two. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { // Radix2FFT is automatically chosen for power-of-two sizes final fft1024 = FFT(1024); // Uses Radix2FFT internally print('FFT(1024): $fft1024'); // For best performance, use power-of-two sizes final sizes = [64, 128, 256, 512, 1024, 2048, 4096]; for (final size in sizes) { final fft = FFT(size); // Generate test signal final signal = List.generate(size, (i) => sin(2 * pi * 10 * i / size) + 0.5 * cos(2 * pi * 25 * i / size) ); // Time the FFT final stopwatch = Stopwatch()..start(); for (int iter = 0; iter < 1000; iter++) { fft.realFft(signal); } stopwatch.stop(); print('Size $size: ${stopwatch.elapsedMicroseconds / 1000} us per FFT'); } // Tip: Zero-pad to next power of two for better performance final nonPow2Signal = List.generate(1000, (i) => sin(2 * pi * i / 1000)); // Option 1: Use FFT of size 1000 (slower, uses CompositeFFT) final fft1000 = FFT(1000); final result1 = fft1000.realFft(nonPow2Signal); // Option 2: Zero-pad to 1024 (faster, uses Radix2FFT) final paddedSignal = List.filled(1024, 0.0); for (int i = 0; i < 1000; i++) paddedSignal[i] = nonPow2Signal[i]; final fft1024fast = FFT(1024); final result2 = fft1024fast.realFft(paddedSignal); print(' Non-power-of-two FFT: $fft1000'); print('Power-of-two FFT: $fft1024fast'); } ``` -------------------------------- ### Run Basic Real-Valued FFT Source: https://github.com/liamappelbe/fftea/blob/main/README.md Use this snippet to perform a basic Fast Fourier Transform on a real-valued data array. Ensure the FFT object is constructed once and reused for multiple FFT operations for better performance. ```dart import 'package:fftea/fftea.dart'; List myData = ...; final fft = FFT(myData.length); final freq = fft.realFft(myData); ``` -------------------------------- ### Streaming STFT with stream() and flush() Source: https://context7.com/liamappelbe/fftea/llms.txt Use `stream()` to process audio data incrementally and `flush()` to process any remaining buffered data. `stream()` does not zero-pad incomplete chunks but retains excess data for future processing. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { const chunkSize = 1024; final stft = STFT(chunkSize, Window.hanning(chunkSize)); // Simulate streaming audio in small packets int processedFrames = 0; void processChunk(Float64x2List freq) { final mags = freq.discardConjugates().magnitudes(); processedFrames++; print('Frame $processedFrames: peak magnitude = ${mags.reduce(max).toStringAsFixed(4)}'); } // Stream packet 1 (smaller than chunk size) final packet1 = List.generate(500, (i) => sin(2 * pi * 440 * i / 44100)); stft.stream(packet1, processChunk, chunkSize ~/ 2); print('After packet 1: $processedFrames frames'); // Stream packet 2 (together with packet 1, enough for at least one chunk) final packet2 = List.generate(800, (i) => sin(2 * pi * 440 * (i + 500) / 44100)); stft.stream(packet2, processChunk, chunkSize ~/ 2); print('After packet 2: $processedFrames frames'); // Stream packet 3 final packet3 = List.generate(1000, (i) => sin(2 * pi * 440 * (i + 1300) / 44100)); stft.stream(packet3, processChunk, chunkSize ~/ 2); print('After packet 3: $processedFrames frames'); // Flush any remaining buffered data (zero-pads if needed) stft.flush(processChunk); print('After flush: $processedFrames frames'); } ``` -------------------------------- ### Basic Real-Valued FFT and Frequency Analysis Source: https://context7.com/liamappelbe/fftea/llms.txt Performs a forward FFT on a real-valued signal and extracts frequency magnitudes. Useful for analyzing the frequency components of a time-domain signal. Ensure the input signal is a List and the FFT object is initialized with the correct size. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; void main() { // Create an FFT object for arrays of size 256 final fft = FFT(256); // Real-valued FFT: converts time-domain signal to frequency domain List signal = List.generate(256, (i) => 0.5 * sin(2 * pi * 10 * i / 256) + // 10 Hz component 0.3 * sin(2 * pi * 50 * i / 256) // 50 Hz component ); final freq = fft.realFft(signal); // Returns Float64x2List (complex numbers) // Get magnitudes (amplitudes) of each frequency bin final magnitudes = freq.magnitudes(); // Float64List of amplitudes // Get frequency at specific index (assuming 256 samples/second) double samplingRate = 256.0; for (int i = 0; i < 20; i++) { double hz = fft.frequency(i, samplingRate); print('Bin $i: ${hz.toStringAsFixed(1)} Hz, magnitude: ${magnitudes[i].toStringAsFixed(4)}'); } // Find the index of a specific frequency double targetFreq = 50.0; double index = fft.indexOfFrequency(targetFreq, samplingRate); print('Index for $targetFreq Hz: ${index.round()}'); } ``` -------------------------------- ### Spectrogram Computation with STFT Source: https://context7.com/liamappelbe/fftea/llms.txt Computes and processes a spectrogram from simulated audio data using the Short-Time Fourier Transform (STFT). Demonstrates STFT configuration, frame processing, and peak frequency identification. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { // Simulate audio: 1 second of a chord (440 Hz + 554 Hz + 659 Hz = A major) const sampleRate = 44100; const duration = 1.0; final numSamples = (sampleRate * duration).toInt(); final audio = Float64List(numSamples); for (int i = 0; i < numSamples; i++) { double t = i / sampleRate; audio[i] = 0.3 * sin(2 * pi * 440 * t) + // A4 0.3 * sin(2 * pi * 554 * t) + // C#5 0.3 * sin(2 * pi * 659 * t); // E5 } // Configure STFT const chunkSize = 4096; // Higher = better frequency resolution const hopSize = 1024; // Lower = better time resolution final window = Window.hanning(chunkSize); final stft = STFT(chunkSize, window); // Compute spectrogram final spectrogram = []; final times = []; int sampleIndex = 0; stft.run(audio, (Float64x2List chunk) { // Store time of this frame (center of window) times.add((sampleIndex + chunkSize / 2) / sampleRate); sampleIndex += hopSize; // Extract magnitudes, discarding conjugate frequencies spectrogram.add(chunk.discardConjugates().magnitudes()); }, hopSize); print('Spectrogram: ${spectrogram.length} frames x ${spectrogram[0].length} frequency bins'); print('Time range: ${times.first.toStringAsFixed(3)}s to ${times.last.toStringAsFixed(3)}s'); // Find peak frequencies in middle frame final midFrame = spectrogram[spectrogram.length ~/ 2]; final peaks = >[]; for (int i = 1; i < midFrame.length - 1; i++) { if (midFrame[i] > midFrame[i-1] && midFrame[i] > midFrame[i+1] && midFrame[i] > 0.1) { peaks.add(MapEntry(i, midFrame[i])); } } peaks.sort((a, b) => b.value.compareTo(a.value)); print(' Top 5 frequency peaks:'); for (int i = 0; i < min(5, peaks.length); i++) { final binIndex = peaks[i].key; final freq = stft.frequency(binIndex, sampleRate.toDouble()); final magnitude = peaks[i].value; print(' ${freq.toStringAsFixed(1)} Hz (magnitude: ${magnitude.toStringAsFixed(4)})'); } } ``` -------------------------------- ### Resample Audio Data Source: https://context7.com/liamappelbe/fftea/llms.txt Use `resample`, `resampleByRatio`, or `resampleByRate` to change the number of samples in audio data. These functions are useful for converting between sample rates or adjusting audio length. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { // Generate test audio at 44100 Hz const originalRate = 44100; const duration = 0.1; // 100ms final audio = List.generate( (originalRate * duration).toInt(), (i) => sin(2 * pi * 440 * i / originalRate), // 440 Hz tone ); print('Original: ${audio.length} samples at $originalRate Hz'); // Resample to specific output length final resampledLength = resample(audio, 2205); // Exactly 2205 samples print('Resampled to length: ${resampledLength.length} samples'); // Resample by ratio (2.0 = double the samples) final doubled = resampleByRatio(audio, 2.0); print('Doubled (ratio 2.0): ${doubled.length} samples'); // Resample by ratio (0.5 = half the samples) final halved = resampleByRatio(audio, 0.5); print('Halved (ratio 0.5): ${halved.length} samples'); // Resample by sample rate conversion const targetRate = 22050; final converted = resampleByRate(audio, originalRate.toDouble(), targetRate.toDouble()); print('Converted to $targetRate Hz: ${converted.length} samples'); // Verify the audio content is preserved print(' Original first 10 samples: ${audio.sublist(0, 10).map((x) => x.toStringAsFixed(4)).join(", ")} '); print('Doubled first 10 samples: ${doubled.sublist(0, 10).map((x) => x.toStringAsFixed(4)).join(", ")} '); } ``` -------------------------------- ### Applying Window Functions in STFT Source: https://context7.com/liamappelbe/fftea/llms.txt Window functions reduce spectral leakage in FFT analysis. Apply windows to complex or real arrays using `applyWindow` (returns new array) or `inPlaceApplyWindow` (modifies original array). ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { const size = 256; // Create different window types final hanning = Window.hanning(size); // Good general-purpose final hamming = Window.hamming(size); // Less sidelobe leakage final bartlett = Window.bartlett(size); // Triangular window final blackman = Window.blackman(size); // Best sidelobe suppression // Custom cosine window with specific amplitude final customCosine = Window.cosine(size, 0.42); // Amplitude parameter // Apply window to complex array (in-place) final complexData = Float64x2List(size); for (int i = 0; i < size; i++) { complexData[i] = Float64x2(sin(2 * pi * 10 * i / size), 0); } hanning.inPlaceApplyWindow(complexData); // Apply window to complex array (returns new array) final original = ComplexArray.fromRealArray( List.generate(size, (i) => sin(2 * pi * 10 * i / size)) ); final windowed = hamming.applyWindow(original); // original unchanged // Apply window to real array (in-place) final realData = Float64List.fromList( List.generate(size, (i) => sin(2 * pi * 10 * i / size)) ); bartlett.inPlaceApplyWindowReal(realData); // Apply window to real array (returns new array) final realOriginal = List.generate(size, (i) => sin(2 * pi * 10 * i / size)); final realWindowed = blackman.applyWindowReal(realOriginal); // Print window values for comparison print('Window comparison at center (i=128):'); print(' Hanning: ${hanning[128].toStringAsFixed(4)}'); print(' Hamming: ${hamming[128].toStringAsFixed(4)}'); print(' Bartlett: ${bartlett[128].toStringAsFixed(4)}'); print(' Blackman: ${blackman[128].toStringAsFixed(4)}'); } ``` -------------------------------- ### Manipulate Complex Arrays with ComplexArray Source: https://context7.com/liamappelbe/fftea/llms.txt Demonstrates converting between real and complex arrays, calculating magnitudes, and handling conjugates for real-valued FFT results. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { // Convert real array to complex array final reals = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; final complex = ComplexArray.fromRealArray(reals); // With optional output length (truncate or zero-pad) final padded = ComplexArray.fromRealArray(reals, 16); // Zero-padded to 16 final truncated = ComplexArray.fromRealArray(reals, 4); // Truncated to 4 // Convert back to real array (discards imaginary parts) final realOnly = complex.toRealArray(); // Perform FFT final fft = FFT(8); final freq = fft.realFft(reals); // Get magnitudes (amplitude of each frequency) final mags = freq.magnitudes(); // Get square magnitudes (more efficient if you need squared values) final sqMags = freq.squareMagnitudes(); // Discard conjugate terms (redundant for real-valued FFT) // Original: [sum, f1, f2, f3, nyquist, conj3, conj2, conj1] // After: [sum, f1, f2, f3, nyquist] final trimmed = freq.discardConjugates(); print('Original length: ${freq.length}, trimmed: ${trimmed.length}'); // Recreate conjugates for inverse FFT final restored = trimmed.createConjugates(8); print('Restored length: ${restored.length}'); // Complex multiplication (modifies array in place) final a = ComplexArray.fromRealArray([1.0, 2.0, 3.0, 4.0]); final b = ComplexArray.fromRealArray([0.5, 0.5, 0.5, 0.5]); fft.inPlaceFft(a); fft.inPlaceFft(b); a.complexMultiply(b); // a = a * b (element-wise complex multiplication) } ``` -------------------------------- ### In-Place FFT and Inverse FFT for Complex Arrays Source: https://context7.com/liamappelbe/fftea/llms.txt Performs FFT and inverse FFT directly on a Float64x2List of complex numbers, modifying the input array in place for maximum efficiency. Use this when memory allocation is a concern or when data is already in complex format. The input must be a Float64x2List. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { final fft = FFT(8); // Create complex array manually (Float64x2 stores [real, imaginary]) final complexData = Float64x2List(8); for (int i = 0; i < 8; i++) { double real = cos(2 * pi * i / 8); // Real component double imag = 0.0; // Imaginary component complexData[i] = Float64x2(real, imag); } // In-place FFT - modifies complexData directly fft.inPlaceFft(complexData); // Print results for (int i = 0; i < complexData.length; i++) { final z = complexData[i]; print('Bin $i: real=${z.x.toStringAsFixed(4)}, imag=${z.y.toStringAsFixed(4)}'); } // In-place inverse FFT to recover original signal fft.inPlaceInverseFft(complexData); print(' Recovered signal:'); for (int i = 0; i < complexData.length; i++) { print('Sample $i: ${complexData[i].x.toStringAsFixed(4)}'); } } ``` -------------------------------- ### Inverse FFT for Real Output with Filtering Source: https://context7.com/liamappelbe/fftea/llms.txt Converts frequency-domain data back to a real-valued time-domain signal after performing modifications in the frequency domain, such as filtering. Note that `realInverseFft` modifies the input array, so pass a copy if the original frequency data is needed. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; import 'dart:math'; void main() { final fft = FFT(256); // Original signal: sum of two sinusoids List original = List.generate(256, (i) => sin(2 * pi * 5 * i / 256) + 0.5 * sin(2 * pi * 20 * i / 256) ); // Forward FFT final freq = fft.realFft(original); // Modify in frequency domain (e.g., low-pass filter - zero out high frequencies) for (int i = 30; i < freq.length; i++) { freq[i] = Float64x2.zero(); } // Inverse FFT back to time domain // WARNING: realInverseFft modifies the input array! final filtered = fft.realInverseFft(freq.sublist(0)); // Pass a copy print('Original first 10 samples: ${original.sublist(0, 10).map((x) => x.toStringAsFixed(3)).join(", ")}'); print('Filtered first 10 samples: ${filtered.sublist(0, 10).map((x) => x.toStringAsFixed(3)).join(", ")}'); } ``` -------------------------------- ### Perform Linear and Circular Convolution Source: https://context7.com/liamappelbe/fftea/llms.txt Use `convolution` for standard filtering and `circularConvolution` for wrap-around effects. Both functions accept signals and kernels, with `circularConvolution` allowing a custom output length. ```dart import 'package:fftea/fftea.dart'; import 'dart:typed_data'; void main() { // Simple moving average filter kernel final kernel = [0.2, 0.2, 0.2, 0.2, 0.2]; // 5-point average // Signal to filter final signal = [1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0]; // Linear convolution (output length = max(a.length, b.length)) final linearResult = convolution(signal, kernel); print('Linear convolution result:'); print(linearResult.map((x) => x.toStringAsFixed(3)).join(', ')); // Circular convolution (wraps around) final circularResult = circularConvolution(signal, kernel); print(' Circular convolution result:'); print(circularResult.map((x) => x.toStringAsFixed(3)).join(', ')); // Circular convolution with custom output length final customLength = circularConvolution(signal, kernel, 16); print(' Circular convolution (length 16):'); print(customLength.map((x) => x.toStringAsFixed(3)).join(', ')); // Reverb-like effect: convolve audio with impulse response final audio = List.generate(1000, (i) => (i < 100) ? 1.0 : 0.0 // Short impulse ); final impulseResponse = List.generate(100, (i) => 0.5 * exp(-i / 20.0) // Exponential decay ); final withReverb = convolution(audio, impulseResponse); print(' Audio with reverb: ${withReverb.length} samples'); } double exp(double x) => 2.718281828 * x; // Simplified for example ``` -------------------------------- ### Discard Conjugates from FFT Result Source: https://github.com/liamappelbe/fftea/blob/main/README.md This code snippet illustrates the removal of redundant conjugate data from a real-valued FFT result. This is a common practice when generating spectrograms, as the latter half of the complex numbers in the FFT output are conjugates of the first half (excluding the Nyquist term). ```dart [sum term, ...terms..., nyquist term, ...conjugate terms...] ^----- These terms are kept ------^ ^- Discarded -^ ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.