]> git.sesse.net Git - nageru/blobdiff - audio_mixer.cpp
Implement the EQ in AudioMixer. (No UI yet.)
[nageru] / audio_mixer.cpp
index 40470f3a18e70c5d43e158303f89e84960d6018e..678f3d9736b870689e6ec5b6bdd5367f6583c4c5 100644 (file)
@@ -6,6 +6,9 @@
 #include <stdio.h>
 #include <endian.h>
 #include <cmath>
+#ifdef __SSE__
+#include <immintrin.h>
+#endif
 
 #include "db.h"
 #include "flags.h"
@@ -77,7 +80,9 @@ void convert_fixed32_to_fp32(float *dst, size_t out_channel, size_t out_num_chan
        }
 }
 
-float find_peak(const float *samples, size_t num_samples)
+float find_peak_plain(const float *samples, size_t num_samples) __attribute__((unused));
+
+float find_peak_plain(const float *samples, size_t num_samples)
 {
        float m = fabs(samples[0]);
        for (size_t i = 1; i < num_samples; ++i) {
@@ -86,6 +91,54 @@ float find_peak(const float *samples, size_t num_samples)
        return m;
 }
 
+#ifdef __SSE__
+static inline float horizontal_max(__m128 m)
+{
+       __m128 tmp = _mm_shuffle_ps(m, m, _MM_SHUFFLE(1, 0, 3, 2));
+       m = _mm_max_ps(m, tmp);
+       tmp = _mm_shuffle_ps(m, m, _MM_SHUFFLE(2, 3, 0, 1));
+       m = _mm_max_ps(m, tmp);
+       return _mm_cvtss_f32(m);
+}
+
+float find_peak(const float *samples, size_t num_samples)
+{
+       const __m128 abs_mask = _mm_castsi128_ps(_mm_set1_epi32(0x7fffffffu));
+       __m128 m = _mm_setzero_ps();
+       for (size_t i = 0; i < (num_samples & ~3); i += 4) {
+               __m128 x = _mm_loadu_ps(samples + i);
+               x = _mm_and_ps(x, abs_mask);
+               m = _mm_max_ps(m, x);
+       }
+       float result = horizontal_max(m);
+
+       for (size_t i = (num_samples & ~3); i < num_samples; ++i) {
+               result = max(result, fabs(samples[i]));
+       }
+
+#if 0
+       // Self-test. We should be bit-exact the same.
+       float reference_result = find_peak_plain(samples, num_samples);
+       if (result != reference_result) {
+               fprintf(stderr, "Error: Peak is %f [%f %f %f %f]; should be %f.\n",
+                       result,
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(0, 0, 0, 0))),
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(1, 1, 1, 1))),
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(2, 2, 2, 2))),
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(3, 3, 3, 3))),
+                       reference_result);
+               abort();
+       }
+#endif
+       return result;
+}
+#else
+float find_peak(const float *samples, size_t num_samples)
+{
+       return find_peak_plain(samples, num_samples);
+}
+#endif
+
 void deinterleave_samples(const vector<float> &in, vector<float> *out_l, vector<float> *out_r)
 {
        size_t num_samples = in.size() / 2;
@@ -111,6 +164,10 @@ AudioMixer::AudioMixer(unsigned num_cards)
        for (unsigned bus_index = 0; bus_index < MAX_BUSES; ++bus_index) {
                locut[bus_index].init(FILTER_HPF, 2);
                locut_enabled[bus_index] = global_flags.locut_enabled;
+               eq[bus_index][EQ_BAND_BASS].init(FILTER_LOW_SHELF, 1);
+               // Note: EQ_BAND_MID isn't used (see comments in apply_eq()).
+               eq[bus_index][EQ_BAND_TREBLE].init(FILTER_HIGH_SHELF, 1);
+
                gain_staging_db[bus_index] = global_flags.initial_gain_staging_db;
                compressor[bus_index].reset(new StereoCompressor(OUTPUT_FREQUENCY));
                compressor_threshold_dbfs[bus_index] = ref_level_dbfs - 12.0f;  // -12 dB.
@@ -211,8 +268,7 @@ bool AudioMixer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsigned
        assert(num_channels > 0);
 
        // Convert the audio to fp32.
-       vector<float> audio;
-       audio.resize(num_samples * num_channels);
+       unique_ptr<float[]> audio(new float[num_samples * num_channels]);
        unsigned channel_index = 0;
        for (auto channel_it = device->interesting_channels.cbegin(); channel_it != device->interesting_channels.end(); ++channel_it, ++channel_index) {
                switch (audio_format.bits_per_sample) {
@@ -220,13 +276,13 @@ bool AudioMixer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsigned
                        assert(num_samples == 0);
                        break;
                case 16:
-                       convert_fixed16_to_fp32(&audio[0], channel_index, num_channels, data, *channel_it, audio_format.num_channels, num_samples);
+                       convert_fixed16_to_fp32(audio.get(), channel_index, num_channels, data, *channel_it, audio_format.num_channels, num_samples);
                        break;
                case 24:
-                       convert_fixed24_to_fp32(&audio[0], channel_index, num_channels, data, *channel_it, audio_format.num_channels, num_samples);
+                       convert_fixed24_to_fp32(audio.get(), channel_index, num_channels, data, *channel_it, audio_format.num_channels, num_samples);
                        break;
                case 32:
-                       convert_fixed32_to_fp32(&audio[0], channel_index, num_channels, data, *channel_it, audio_format.num_channels, num_samples);
+                       convert_fixed32_to_fp32(audio.get(), channel_index, num_channels, data, *channel_it, audio_format.num_channels, num_samples);
                        break;
                default:
                        fprintf(stderr, "Cannot handle audio with %u bits per sample\n", audio_format.bits_per_sample);
@@ -236,7 +292,7 @@ bool AudioMixer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsigned
 
        // Now add it.
        int64_t local_pts = device->next_local_pts;
-       device->resampling_queue->add_input_samples(local_pts / double(TIMEBASE), audio.data(), num_samples);
+       device->resampling_queue->add_input_samples(local_pts / double(TIMEBASE), audio.get(), num_samples);
        device->next_local_pts = local_pts + frame_length;
        return true;
 }
@@ -357,14 +413,7 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
        samples_bus.resize(num_samples * 2);
        for (unsigned bus_index = 0; bus_index < input_mapping.buses.size(); ++bus_index) {
                fill_audio_bus(samples_card, input_mapping.buses[bus_index], num_samples, &samples_bus[0]);
-
-               // Cut away everything under 120 Hz (or whatever the cutoff is);
-               // we don't need it for voice, and it will reduce headroom
-               // and confuse the compressor. (In particular, any hums at 50 or 60 Hz
-               // should be dampened.)
-               if (locut_enabled[bus_index]) {
-                       locut[bus_index].render(samples_bus.data(), samples_bus.size() / 2, locut_cutoff_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f);
-               }
+               apply_eq(bus_index, &samples_bus);
 
                {
                        lock_guard<mutex> lock(compressor_mutex);
@@ -410,20 +459,9 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
                        }
                }
 
-               // TODO: We should measure post-fader.
+               add_bus_to_master(bus_index, samples_bus, &samples_out);
                deinterleave_samples(samples_bus, &left, &right);
                measure_bus_levels(bus_index, left, right);
-
-               float volume = from_db(fader_volume_db[bus_index]);
-               if (bus_index == 0) {
-                       for (unsigned i = 0; i < num_samples * 2; ++i) {
-                               samples_out[i] = samples_bus[i] * volume;
-                       }
-               } else {
-                       for (unsigned i = 0; i < num_samples * 2; ++i) {
-                               samples_out[i] += samples_bus[i] * volume;
-                       }
-               }
        }
 
        {
@@ -490,12 +528,122 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
        return samples_out;
 }
 
+void AudioMixer::apply_eq(unsigned bus_index, vector<float> *samples_bus)
+{
+       constexpr float bass_freq_hz = 200.0f;
+       constexpr float treble_freq_hz = 4700.0f;
+
+       // Cut away everything under 120 Hz (or whatever the cutoff is);
+       // we don't need it for voice, and it will reduce headroom
+       // and confuse the compressor. (In particular, any hums at 50 or 60 Hz
+       // should be dampened.)
+       if (locut_enabled[bus_index]) {
+               locut[bus_index].render(samples_bus->data(), samples_bus->size() / 2, locut_cutoff_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f);
+       }
+
+       // Apply the rest of the EQ. Since we only have a simple three-band EQ,
+       // we can implement it with two shelf filters. We use a simple gain to
+       // set the mid-level filter, and then offset the low and high bands
+       // from that if we need to. (We could perhaps have folded the gain into
+       // the next part, but it's so cheap that the trouble isn't worth it.)
+       if (fabs(eq_level_db[bus_index][EQ_BAND_MID]) > 0.01f) {
+               float g = from_db(eq_level_db[bus_index][EQ_BAND_MID]);
+               for (size_t i = 0; i < samples_bus->size(); ++i) {
+                       (*samples_bus)[i] *= g;
+               }
+       }
+
+       float bass_adj_db = eq_level_db[bus_index][EQ_BAND_BASS] - eq_level_db[bus_index][EQ_BAND_MID];
+       if (fabs(bass_adj_db) > 0.01f) {
+               eq[bus_index][EQ_BAND_BASS].render(samples_bus->data(), samples_bus->size() / 2,
+                       bass_freq_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f, bass_adj_db / 40.0f);
+       }
+
+       float treble_adj_db = eq_level_db[bus_index][EQ_BAND_TREBLE] - eq_level_db[bus_index][EQ_BAND_MID];
+       if (fabs(treble_adj_db) > 0.01f) {
+               eq[bus_index][EQ_BAND_TREBLE].render(samples_bus->data(), samples_bus->size() / 2,
+                       treble_freq_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f, treble_adj_db / 40.0f);
+       }
+}
+
+void AudioMixer::add_bus_to_master(unsigned bus_index, const vector<float> &samples_bus, vector<float> *samples_out)
+{
+       assert(samples_bus.size() == samples_out->size());
+       assert(samples_bus.size() % 2 == 0);
+       unsigned num_samples = samples_bus.size() / 2;
+       if (fabs(fader_volume_db[bus_index] - last_fader_volume_db[bus_index]) > 1e-3) {
+               // The volume has changed; do a fade over the course of this frame.
+               // (We might have some numerical issues here, but it seems to sound OK.)
+               // For the purpose of fading here, the silence floor is set to -90 dB
+               // (the fader only goes to -84).
+               float old_volume = from_db(max<float>(last_fader_volume_db[bus_index], -90.0f));
+               float volume = from_db(max<float>(fader_volume_db[bus_index], -90.0f));
+
+               float volume_inc = pow(volume / old_volume, 1.0 / num_samples);
+               volume = old_volume;
+               if (bus_index == 0) {
+                       for (unsigned i = 0; i < num_samples; ++i) {
+                               (*samples_out)[i * 2 + 0] = samples_bus[i * 2 + 0] * volume;
+                               (*samples_out)[i * 2 + 1] = samples_bus[i * 2 + 1] * volume;
+                               volume *= volume_inc;
+                       }
+               } else {
+                       for (unsigned i = 0; i < num_samples; ++i) {
+                               (*samples_out)[i * 2 + 0] += samples_bus[i * 2 + 0] * volume;
+                               (*samples_out)[i * 2 + 1] += samples_bus[i * 2 + 1] * volume;
+                               volume *= volume_inc;
+                       }
+               }
+       } else {
+               float volume = from_db(fader_volume_db[bus_index]);
+               if (bus_index == 0) {
+                       for (unsigned i = 0; i < num_samples; ++i) {
+                               (*samples_out)[i * 2 + 0] = samples_bus[i * 2 + 0] * volume;
+                               (*samples_out)[i * 2 + 1] = samples_bus[i * 2 + 1] * volume;
+                       }
+               } else {
+                       for (unsigned i = 0; i < num_samples; ++i) {
+                               (*samples_out)[i * 2 + 0] += samples_bus[i * 2 + 0] * volume;
+                               (*samples_out)[i * 2 + 1] += samples_bus[i * 2 + 1] * volume;
+                       }
+               }
+       }
+
+       last_fader_volume_db[bus_index] = fader_volume_db[bus_index];
+}
+
 void AudioMixer::measure_bus_levels(unsigned bus_index, const vector<float> &left, const vector<float> &right)
 {
-       const float *ptrs[] = { left.data(), right.data() };
-       {
-               lock_guard<mutex> lock(audio_measure_mutex);
-               bus_r128[bus_index]->process(left.size(), const_cast<float **>(ptrs));
+       assert(left.size() == right.size());
+       const float volume = from_db(fader_volume_db[bus_index]);
+       const float peak_levels[2] = {
+               find_peak(left.data(), left.size()) * volume,
+               find_peak(right.data(), right.size()) * volume
+       };
+       for (unsigned channel = 0; channel < 2; ++channel) {
+               // Compute the current value, including hold and falloff.
+               // The constants are borrowed from zita-mu1 by Fons Adriaensen.
+               static constexpr float hold_sec = 0.5f;
+               static constexpr float falloff_db_sec = 15.0f;  // dB/sec falloff after hold.
+               float current_peak;
+               PeakHistory &history = peak_history[bus_index][channel];
+               history.historic_peak = max(history.historic_peak, peak_levels[channel]);
+               if (history.age_seconds < hold_sec) {
+                       current_peak = history.last_peak;
+               } else {
+                       current_peak = history.last_peak * from_db(-falloff_db_sec * (history.age_seconds - hold_sec));
+               }
+
+               // See if we have a new peak to replace the old (possibly falling) one.
+               if (peak_levels[channel] > current_peak) {
+                       history.last_peak = peak_levels[channel];
+                       history.age_seconds = 0.0f;  // Not 100% correct, but more than good enough given our frame sizes.
+                       current_peak = peak_levels[channel];
+               } else {
+                       history.age_seconds += float(left.size()) / OUTPUT_FREQUENCY;
+               }
+               history.current_level = peak_levels[channel];
+               history.current_peak = current_peak;
        }
 }
 
@@ -557,9 +705,23 @@ void AudioMixer::send_audio_level_callback()
 
        vector<BusLevel> bus_levels;
        bus_levels.resize(input_mapping.buses.size());
-       for (unsigned bus_index = 0; bus_index < bus_r128.size(); ++bus_index) {
-               bus_levels[bus_index].loudness_lufs = bus_r128[bus_index]->loudness_S();
-               bus_levels[bus_index].gain_staging_db = gain_staging_db[bus_index];
+       {
+               lock_guard<mutex> lock(compressor_mutex);
+               for (unsigned bus_index = 0; bus_index < bus_levels.size(); ++bus_index) {
+                       bus_levels[bus_index].current_level_dbfs[0] = to_db(peak_history[bus_index][0].current_level);
+                       bus_levels[bus_index].current_level_dbfs[1] = to_db(peak_history[bus_index][1].current_level);
+                       bus_levels[bus_index].peak_level_dbfs[0] = to_db(peak_history[bus_index][0].current_peak);
+                       bus_levels[bus_index].peak_level_dbfs[1] = to_db(peak_history[bus_index][1].current_peak);
+                       bus_levels[bus_index].historic_peak_dbfs = to_db(
+                               max(peak_history[bus_index][0].historic_peak,
+                                   peak_history[bus_index][1].historic_peak));
+                       bus_levels[bus_index].gain_staging_db = gain_staging_db[bus_index];
+                       if (compressor_enabled[bus_index]) {
+                               bus_levels[bus_index].compressor_attenuation_db = -to_db(compressor[bus_index]->get_attenuation());
+                       } else {
+                               bus_levels[bus_index].compressor_attenuation_db = 0.0;
+                       }
+               }
        }
 
        audio_level_callback(loudness_s, to_db(peak), bus_levels,
@@ -633,17 +795,6 @@ void AudioMixer::set_input_mapping(const InputMapping &new_input_mapping)
                }
        }
 
-       {
-               lock_guard<mutex> lock(audio_measure_mutex);
-               bus_r128.resize(new_input_mapping.buses.size());
-               for (unsigned bus_index = 0; bus_index < bus_r128.size(); ++bus_index) {
-                       if (bus_r128[bus_index] == nullptr) {
-                               bus_r128[bus_index].reset(new Ebu_r128_proc);
-                       }
-                       bus_r128[bus_index]->init(2, OUTPUT_FREQUENCY);
-               }
-       }
-
        input_mapping = new_input_mapping;
 }
 
@@ -652,3 +803,16 @@ InputMapping AudioMixer::get_input_mapping() const
        lock_guard<timed_mutex> lock(audio_mutex);
        return input_mapping;
 }
+
+void AudioMixer::reset_peak(unsigned bus_index)
+{
+       lock_guard<timed_mutex> lock(audio_mutex);
+       for (unsigned channel = 0; channel < 2; ++channel) {
+               PeakHistory &history = peak_history[bus_index][channel];
+               history.current_level = 0.0f;
+               history.historic_peak = 0.0f;
+               history.current_peak = 0.0f;
+               history.last_peak = 0.0f;
+               history.age_seconds = 0.0f;
+       }
+}