| 71 | |
| 72 | |
| 73 | void Gemma4e::extract_spectrogram(std::vector<audio_data_t>& audio_inputs, gemma4e_audio_payload_t& audio_payload) { |
| 74 | |
| 75 | audio_payload.num_audios = static_cast<unsigned int>(audio_inputs.size()); |
| 76 | audio_payload.mel_spectrograms.resize(audio_payload.num_audios); |
| 77 | audio_payload.mel_spectrogram_frames_per_audio.resize(audio_payload.num_audios); |
| 78 | audio_payload.mel_spectrogram_bins_per_audio.resize(audio_payload.num_audios); |
| 79 | |
| 80 | // ------- Config (matches Python __init__ defaults) ------- |
| 81 | constexpr float frame_length_ms = 20.0f; |
| 82 | constexpr float hop_length_ms = 10.0f; |
| 83 | constexpr float min_frequency = 0.0f; |
| 84 | constexpr float max_frequency = 8000.0f; |
| 85 | constexpr float mel_floor = 1e-3f; |
| 86 | constexpr int feature_size = 128; // num_mel_filters |
| 87 | constexpr float dither = 0.0f; |
| 88 | constexpr float input_scale_factor = 1.0f; |
| 89 | constexpr float preemphasis = 0.0f; |
| 90 | constexpr bool preemphasis_htk_flavor = true; |
| 91 | constexpr bool fft_overdrive = false; |
| 92 | |
| 93 | for (unsigned int audio_idx = 0; audio_idx < audio_payload.num_audios; audio_idx++) { |
| 94 | audio_data_t& audio_input = audio_inputs[audio_idx]; |
| 95 | |
| 96 | const int sampling_rate = audio_input.sample_rate; |
| 97 | |
| 98 | // frame_length = int(round(sampling_rate * frame_length_ms / 1000.0)) |
| 99 | const int frame_length = static_cast<int>(std::round(sampling_rate * frame_length_ms / 1000.0f)); |
| 100 | // hop_length = int(round(sampling_rate * hop_length_ms / 1000.0)) |
| 101 | const int hop_length = static_cast<int>(std::round(sampling_rate * hop_length_ms / 1000.0f)); |
| 102 | |
| 103 | // fft_length = 2 ** ceil(log2(frame_length)) |
| 104 | int fft_length = 1; |
| 105 | while (fft_length < frame_length) fft_length <<= 1; |
| 106 | if (fft_overdrive) fft_length *= 2; |
| 107 | |
| 108 | const int num_frequency_bins = fft_length / 2 + 1; |
| 109 | |
| 110 | // ------- self.window = window_function(frame_length).astype(np.float32) ------- |
| 111 | // periodic Hann window, matching Python: window_function(frame_length) |
| 112 | // Python default: name="hann", periodic=True |
| 113 | std::vector<float> window = audioproc::window_function_optimized(frame_length, "hann", /*periodic=*/true); |
| 114 | |
| 115 | // ------- self.mel_filters = mel_filter_bank(...) ------- |
| 116 | std::vector<float> mel_filters = audioproc::mel_filter_bank_optimized( |
| 117 | num_frequency_bins, feature_size, |
| 118 | min_frequency, max_frequency, |
| 119 | sampling_rate, /*apply_slaney_norm=*/false); |
| 120 | |
| 121 | // ------- waveform = audio_input.samples (mono, 1D) ------- |
| 122 | // The Python code works on [B, T]. We handle B=1 (single waveform). |
| 123 | const float* waveform_ptr = audio_input.samples.data(); |
| 124 | const int original_length = static_cast<int>(audio_input.num_frames); |
| 125 | |
| 126 | // ------- Semicausal time padding: prepend frame_length // 2 zeros ------- |
| 127 | // waveform = np.pad(waveform, ((0,0), (pad_left, 0)), mode="constant") |
| 128 | const int pad_left = frame_length / 2; |
| 129 | const int padded_length = original_length + pad_left; |
| 130 | std::vector<float> waveform(padded_length, 0.0f); |
no test coverage detected