///|
/// Computes positive-frequency magnitude frames and their time coordinates.
pub fn spectrogram(
signal : ArrayView[Double],
window_kind : WindowKind,
window_length~ : Int,
hop_size~ : Int,
sample_rate~ : Double,
) -> SpectrogramResult raise SpectrumError {
if signal.length() == 0 {
raise EmptySignal
}
if sample_rate <= 0.0 {
raise InvalidArgument(message="sample_rate must be positive")
}
let frames = stft(signal, window_kind, window_length, hop_size)
let half = window_length / 2
let frequencies : Array[Double] = []
for i in 0..<=half {
frequencies.push(
Double::from_int(i) * sample_rate / Double::from_int(window_length),
)
}
let frame_times : Array[Double] = []
let magnitudes : Array[Array[Double]] = []
for frame_index, frame in frames {
let all_magnitudes = magnitude_spectrum(frame)
frame_times.push(Double::from_int(frame_index * hop_size) / sample_rate)
let end = half + 1
magnitudes.push(all_magnitudes[0:end].to_owned())
}
{ frequencies, frame_times, magnitudes }
}
///|
/// Returns the sum of squared positive-frequency magnitudes for each frame.
pub fn spectrogram_energy(result : SpectrogramResult) -> Array[Double] {
result.magnitudes.map(frame => {
frame.fold(init=0.0, (total, value) => total + value * value)
})
}
///|
/// Returns the dominant positive-frequency peak for every spectrogram frame.
pub fn spectrogram_dominant_peaks(
result : SpectrogramResult,
) -> Array[Peak] raise SpectrumError {
if result.magnitudes.length() == 0 {
raise EmptySignal
}
let peaks : Array[Peak] = []
for frame in result.magnitudes {
let mut best = 0
for i in 1.. frame[best] {
best = i
}
}
peaks.push({
index: best,
frequency: result.frequencies[best],
magnitude: frame[best],
})
}
peaks
}
///|
/// Returns the frame index with the highest total energy.
pub fn spectrogram_loudest_frame(
result : SpectrogramResult,
) -> Int raise SpectrumError {
let energies = spectrogram_energy(result)
if energies.length() == 0 {
raise EmptySignal
}
let mut best = 0
for i in 1.. energies[best] {
best = i
}
}
best
}