From 6fe61fb9769469b573d84d1b9f06c7316b6937ed Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 23 Aug 2016 00:00:18 +0200 Subject: [PATCH] Move the gain staging and compressors into each bus, and hook up the corresponding controls. --- audio_mixer.cpp | 105 ++++++++++++++++++++++++------------------------ audio_mixer.h | 48 +++++++++++----------- mainwindow.cpp | 92 +++++++++++++++++++++++++++++++----------- mainwindow.h | 6 +-- 4 files changed, 148 insertions(+), 103 deletions(-) diff --git a/audio_mixer.cpp b/audio_mixer.cpp index 03c894c..3b7d3e5 100644 --- a/audio_mixer.cpp +++ b/audio_mixer.cpp @@ -105,18 +105,19 @@ void deinterleave_samples(const vector &in, vector *out_l, vector< AudioMixer::AudioMixer(unsigned num_cards) : num_cards(num_cards), - level_compressor(OUTPUT_FREQUENCY), limiter(OUTPUT_FREQUENCY), - compressor(OUTPUT_FREQUENCY), correlation(OUTPUT_FREQUENCY) { 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; + 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. + compressor_enabled[bus_index] = global_flags.compressor_enabled; + level_compressor[bus_index].reset(new StereoCompressor(OUTPUT_FREQUENCY)); + level_compressor_enabled[bus_index] = global_flags.gain_staging_auto; } - set_gain_staging_db(global_flags.initial_gain_staging_db); - set_gain_staging_auto(global_flags.gain_staging_auto); - set_compressor_enabled(global_flags.compressor_enabled); set_limiter_enabled(global_flags.limiter_enabled); set_final_makeup_gain_auto(global_flags.final_makeup_gain_auto); @@ -351,7 +352,6 @@ vector AudioMixer::get_output(double pts, unsigned num_samples, Resamplin } } - // TODO: Move lo-cut etc. into each bus. vector samples_out, left, right; samples_out.resize(num_samples * 2); samples_bus.resize(num_samples * 2); @@ -366,6 +366,50 @@ vector AudioMixer::get_output(double pts, unsigned num_samples, Resamplin locut[bus_index].render(samples_bus.data(), samples_bus.size() / 2, locut_cutoff_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f); } + { + lock_guard lock(compressor_mutex); + + // Apply a level compressor to get the general level right. + // Basically, if it's over about -40 dBFS, we squeeze it down to that level + // (or more precisely, near it, since we don't use infinite ratio), + // then apply a makeup gain to get it to -14 dBFS. -14 dBFS is, of course, + // entirely arbitrary, but from practical tests with speech, it seems to + // put ut around -23 LUFS, so it's a reasonable starting point for later use. + if (level_compressor_enabled[bus_index]) { + float threshold = 0.01f; // -40 dBFS. + float ratio = 20.0f; + float attack_time = 0.5f; + float release_time = 20.0f; + float makeup_gain = from_db(ref_level_dbfs - (-40.0f)); // +26 dB. + level_compressor[bus_index]->process(samples_bus.data(), samples_bus.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain); + gain_staging_db[bus_index] = to_db(level_compressor[bus_index]->get_attenuation() * makeup_gain); + } else { + // Just apply the gain we already had. + float g = from_db(gain_staging_db[bus_index]); + for (size_t i = 0; i < samples_bus.size(); ++i) { + samples_bus[i] *= g; + } + } + +#if 0 + printf("level=%f (%+5.2f dBFS) attenuation=%f (%+5.2f dB) end_result=%+5.2f dB\n", + level_compressor.get_level(), to_db(level_compressor.get_level()), + level_compressor.get_attenuation(), to_db(level_compressor.get_attenuation()), + to_db(level_compressor.get_level() * level_compressor.get_attenuation() * makeup_gain)); +#endif + + // The real compressor. + if (compressor_enabled[bus_index]) { + float threshold = from_db(compressor_threshold_dbfs[bus_index]); + float ratio = 20.0f; + float attack_time = 0.005f; + float release_time = 0.040f; + float makeup_gain = 2.0f; // +6 dB. + compressor[bus_index]->process(samples_bus.data(), samples_bus.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain); + // compressor_att = compressor.get_attenuation(); + } + } + // TODO: We should measure post-fader. deinterleave_samples(samples_bus, &left, &right); measure_bus_levels(bus_index, left, right); @@ -385,50 +429,6 @@ vector AudioMixer::get_output(double pts, unsigned num_samples, Resamplin { lock_guard lock(compressor_mutex); - // Apply a level compressor to get the general level right. - // Basically, if it's over about -40 dBFS, we squeeze it down to that level - // (or more precisely, near it, since we don't use infinite ratio), - // then apply a makeup gain to get it to -14 dBFS. -14 dBFS is, of course, - // entirely arbitrary, but from practical tests with speech, it seems to - // put ut around -23 LUFS, so it's a reasonable starting point for later use. - { - if (level_compressor_enabled) { - float threshold = 0.01f; // -40 dBFS. - float ratio = 20.0f; - float attack_time = 0.5f; - float release_time = 20.0f; - float makeup_gain = from_db(ref_level_dbfs - (-40.0f)); // +26 dB. - level_compressor.process(samples_out.data(), samples_out.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain); - gain_staging_db = to_db(level_compressor.get_attenuation() * makeup_gain); - } else { - // Just apply the gain we already had. - float g = from_db(gain_staging_db); - for (size_t i = 0; i < samples_out.size(); ++i) { - samples_out[i] *= g; - } - } - } - - #if 0 - printf("level=%f (%+5.2f dBFS) attenuation=%f (%+5.2f dB) end_result=%+5.2f dB\n", - level_compressor.get_level(), to_db(level_compressor.get_level()), - level_compressor.get_attenuation(), to_db(level_compressor.get_attenuation()), - to_db(level_compressor.get_level() * level_compressor.get_attenuation() * makeup_gain)); - #endif - - // float limiter_att, compressor_att; - - // The real compressor. - if (compressor_enabled) { - float threshold = from_db(compressor_threshold_dbfs); - float ratio = 20.0f; - float attack_time = 0.005f; - float release_time = 0.040f; - float makeup_gain = 2.0f; // +6 dB. - compressor.process(samples_out.data(), samples_out.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain); - // compressor_att = compressor.get_attenuation(); - } - // Finally a limiter at -4 dB (so, -10 dBFS) to take out the worst peaks only. // Note that since ratio is not infinite, we could go slightly higher than this. if (limiter_enabled) { @@ -444,7 +444,8 @@ vector AudioMixer::get_output(double pts, unsigned num_samples, Resamplin // printf("limiter=%+5.1f compressor=%+5.1f\n", to_db(limiter_att), to_db(compressor_att)); } - // At this point, we are most likely close to +0 LU, but all of our + // At this point, we are most likely close to +0 LU (at least if the + // faders sum to 0 dB and the compressors are on), but all of our // measurements have been on raw sample values, not R128 values. // So we have a final makeup gain to get us to +0 LU; the gain // adjustments required should be relatively small, and also, the @@ -562,7 +563,7 @@ void AudioMixer::send_audio_level_callback() audio_level_callback(loudness_s, to_db(peak), bus_loudness, loudness_i, loudness_range_low, loudness_range_high, - gain_staging_db, + vector(gain_staging_db, gain_staging_db + MAX_BUSES), to_db(final_makeup_gain), correlation.get_correlation()); } diff --git a/audio_mixer.h b/audio_mixer.h index 0c401d5..d00e8ec 100644 --- a/audio_mixer.h +++ b/audio_mixer.h @@ -116,9 +116,9 @@ public: return limiter_threshold_dbfs; } - float get_compressor_threshold_dbfs() const + float get_compressor_threshold_dbfs(unsigned bus_index) const { - return compressor_threshold_dbfs; + return compressor_threshold_dbfs[bus_index]; } void set_limiter_threshold_dbfs(float threshold_dbfs) @@ -126,9 +126,9 @@ public: limiter_threshold_dbfs = threshold_dbfs; } - void set_compressor_threshold_dbfs(float threshold_dbfs) + void set_compressor_threshold_dbfs(unsigned bus_index, float threshold_dbfs) { - compressor_threshold_dbfs = threshold_dbfs; + compressor_threshold_dbfs[bus_index] = threshold_dbfs; } void set_limiter_enabled(bool enabled) @@ -141,39 +141,39 @@ public: return limiter_enabled; } - void set_compressor_enabled(bool enabled) + void set_compressor_enabled(unsigned bus_index, bool enabled) { - compressor_enabled = enabled; + compressor_enabled[bus_index] = enabled; } - bool get_compressor_enabled() const + bool get_compressor_enabled(unsigned bus_index) const { - return compressor_enabled; + return compressor_enabled[bus_index]; } - void set_gain_staging_db(float gain_db) + void set_gain_staging_db(unsigned bus_index, float gain_db) { std::unique_lock lock(compressor_mutex); - level_compressor_enabled = false; - gain_staging_db = gain_db; + level_compressor_enabled[bus_index] = false; + gain_staging_db[bus_index] = gain_db; } - float get_gain_staging_db() const + float get_gain_staging_db(unsigned bus_index) const { std::unique_lock lock(compressor_mutex); - return gain_staging_db; + return gain_staging_db[bus_index]; } - void set_gain_staging_auto(bool enabled) + void set_gain_staging_auto(unsigned bus_index, bool enabled) { std::unique_lock lock(compressor_mutex); - level_compressor_enabled = enabled; + level_compressor_enabled[bus_index] = enabled; } - bool get_gain_staging_auto() const + bool get_gain_staging_auto(unsigned bus_index) const { std::unique_lock lock(compressor_mutex); - return level_compressor_enabled; + return level_compressor_enabled[bus_index]; } void set_final_makeup_gain_db(float gain_db) @@ -204,7 +204,7 @@ public: typedef std::function bus_level_lufs, float global_level_lufs, float range_low_lufs, float range_high_lufs, - float gain_staging_db, float final_makeup_gain_db, + std::vector gain_staging_db, float final_makeup_gain_db, float correlation)> audio_level_callback_t; void set_audio_level_callback(audio_level_callback_t callback) { @@ -249,9 +249,9 @@ private: // First compressor; takes us up to about -12 dBFS. mutable std::mutex compressor_mutex; - StereoCompressor level_compressor; // Under compressor_mutex. Used to set/override gain_staging_db if . - float gain_staging_db = 0.0f; // Under compressor_mutex. - bool level_compressor_enabled = true; // Under compressor_mutex. + std::unique_ptr level_compressor[MAX_BUSES]; // Under compressor_mutex. Used to set/override gain_staging_db if . + float gain_staging_db[MAX_BUSES]; // Under compressor_mutex. + bool level_compressor_enabled[MAX_BUSES]; // Under compressor_mutex. static constexpr float ref_level_dbfs = -14.0f; // Chosen so that we end up around 0 LU in practice. static constexpr float ref_level_lufs = -23.0f; // 0 LU, more or less by definition. @@ -259,9 +259,9 @@ private: StereoCompressor limiter; std::atomic limiter_threshold_dbfs{ref_level_dbfs + 4.0f}; // 4 dB. std::atomic limiter_enabled{true}; - StereoCompressor compressor; - std::atomic compressor_threshold_dbfs{ref_level_dbfs - 12.0f}; // -12 dB. - std::atomic compressor_enabled{true}; + std::unique_ptr compressor[MAX_BUSES]; + std::atomic compressor_threshold_dbfs[MAX_BUSES]; + std::atomic compressor_enabled[MAX_BUSES]; double final_makeup_gain = 1.0; // Under compressor_mutex. Read/write by the user. Note: Not in dB, we want the numeric precision so that we can change it slowly. bool final_makeup_gain_auto = true; // Under compressor_mutex. diff --git a/mainwindow.cpp b/mainwindow.cpp index 0237106..ac59305 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -237,12 +237,31 @@ void MainWindow::mixer_created(Mixer *mixer) connect(ui->locut_enabled, &QCheckBox::stateChanged, [this](int state){ global_mixer->get_audio_mixer()->set_locut_enabled(0, state == Qt::Checked); }); -#else - ui->locut_enabled->setVisible(false); -#endif ui->gainstaging_knob->setValue(global_mixer->get_audio_mixer()->get_gain_staging_db()); ui->gainstaging_auto_checkbox->setChecked(global_mixer->get_audio_mixer()->get_gain_staging_auto()); ui->compressor_enabled->setChecked(global_mixer->get_audio_mixer()->get_compressor_enabled()); + connect(ui->gainstaging_knob, &QAbstractSlider::valueChanged, this, &MainWindow::gain_staging_knob_changed); + connect(ui->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this](int state){ + global_mixer->get_audio_mixer()->set_gain_staging_auto(state == Qt::Checked); + }); + ui->compressor_threshold_db_display->setText( + QString::fromStdString(format_db(mixer->get_audio_mixer()->get_compressor_threshold_dbfs(), DB_WITH_SIGN))); + ui->compressor_threshold_db_display->setText(buf); + connect(ui->compressor_threshold_knob, &QDial::valueChanged, this, &MainWindow::compressor_threshold_knob_changed); + connect(ui->compressor_enabled, &QCheckBox::stateChanged, [this](int state){ + global_mixer->get_audio_mixer()->set_compressor_enabled(state == Qt::Checked); + }); +#else + ui->locut_enabled->setVisible(false); + ui->gainstaging_label->setVisible(false); + ui->gainstaging_knob->setVisible(false); + ui->gainstaging_db_display->setVisible(false); + ui->gainstaging_auto_checkbox->setVisible(false); + ui->compressor_threshold_label->setVisible(false); + ui->compressor_threshold_knob->setVisible(false); + ui->compressor_threshold_db_display->setVisible(false); + ui->compressor_enabled->setVisible(false); +#endif ui->limiter_enabled->setChecked(global_mixer->get_audio_mixer()->get_limiter_enabled()); ui->makeup_gain_auto_checkbox->setChecked(global_mixer->get_audio_mixer()->get_final_makeup_gain_auto()); @@ -250,29 +269,19 @@ void MainWindow::mixer_created(Mixer *mixer) QString::fromStdString(format_db(mixer->get_audio_mixer()->get_limiter_threshold_dbfs(), DB_WITH_SIGN))); ui->limiter_threshold_db_display->setText(limiter_threshold_label); ui->limiter_threshold_db_display_2->setText(limiter_threshold_label); - ui->compressor_threshold_db_display->setText( - QString::fromStdString(format_db(mixer->get_audio_mixer()->get_compressor_threshold_dbfs(), DB_WITH_SIGN))); connect(ui->locut_cutoff_knob, &QDial::valueChanged, this, &MainWindow::cutoff_knob_changed); cutoff_knob_changed(ui->locut_cutoff_knob->value()); - connect(ui->gainstaging_knob, &QAbstractSlider::valueChanged, this, &MainWindow::gain_staging_knob_changed); - connect(ui->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this](int state){ - global_mixer->get_audio_mixer()->set_gain_staging_auto(state == Qt::Checked); - }); connect(ui->makeup_gain_knob, &QAbstractSlider::valueChanged, this, &MainWindow::final_makeup_gain_knob_changed); connect(ui->makeup_gain_auto_checkbox, &QCheckBox::stateChanged, [this](int state){ global_mixer->get_audio_mixer()->set_final_makeup_gain_auto(state == Qt::Checked); }); connect(ui->limiter_threshold_knob, &QDial::valueChanged, this, &MainWindow::limiter_threshold_knob_changed); - connect(ui->compressor_threshold_knob, &QDial::valueChanged, this, &MainWindow::compressor_threshold_knob_changed); connect(ui->limiter_enabled, &QCheckBox::stateChanged, [this](int state){ global_mixer->get_audio_mixer()->set_limiter_enabled(state == Qt::Checked); }); - connect(ui->compressor_enabled, &QCheckBox::stateChanged, [this](int state){ - global_mixer->get_audio_mixer()->set_compressor_enabled(state == Qt::Checked); - }); connect(ui->reset_meters_button, &QPushButton::clicked, this, &MainWindow::reset_meters_button_clicked); mixer->get_audio_mixer()->set_audio_level_callback(bind(&MainWindow::audio_level_callback, this, _1, _2, _3, _4, _5, _6, _7, _8, _9)); @@ -340,10 +349,24 @@ void MainWindow::setup_audio_expanded_view() ui->buses->addWidget(channel); ui_audio_expanded_view->locut_enabled->setChecked(global_mixer->get_audio_mixer()->get_locut_enabled(bus_index)); - connect(ui->locut_enabled, &QCheckBox::stateChanged, [this, bus_index](int state){ + connect(ui_audio_expanded_view->locut_enabled, &QCheckBox::stateChanged, [this, bus_index](int state){ global_mixer->get_audio_mixer()->set_locut_enabled(bus_index, state == Qt::Checked); }); + ui_audio_expanded_view->gainstaging_knob->setValue(global_mixer->get_audio_mixer()->get_gain_staging_db(bus_index)); + ui_audio_expanded_view->gainstaging_auto_checkbox->setChecked(global_mixer->get_audio_mixer()->get_gain_staging_auto(bus_index)); + ui_audio_expanded_view->compressor_enabled->setChecked(global_mixer->get_audio_mixer()->get_compressor_enabled(bus_index)); + + connect(ui_audio_expanded_view->gainstaging_knob, &QAbstractSlider::valueChanged, bind(&MainWindow::gain_staging_knob_changed, this, bus_index, _1)); + connect(ui_audio_expanded_view->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this, bus_index](int state){ + global_mixer->get_audio_mixer()->set_gain_staging_auto(bus_index, state == Qt::Checked); + }); + + connect(ui_audio_expanded_view->compressor_threshold_knob, &QDial::valueChanged, bind(&MainWindow::compressor_threshold_knob_changed, this, bus_index, _1)); + connect(ui_audio_expanded_view->compressor_enabled, &QCheckBox::stateChanged, [this, bus_index](int state){ + global_mixer->get_audio_mixer()->set_compressor_enabled(bus_index, state == Qt::Checked); + }); + slave_fader(audio_miniviews[bus_index]->fader, ui_audio_expanded_view->fader); } } @@ -390,12 +413,17 @@ void MainWindow::input_mapping_triggered() } } -void MainWindow::gain_staging_knob_changed(int value) +void MainWindow::gain_staging_knob_changed(unsigned bus_index, int value) { - ui->gainstaging_auto_checkbox->setCheckState(Qt::Unchecked); + if (bus_index == 0) { + ui->gainstaging_auto_checkbox->setCheckState(Qt::Unchecked); + } + if (bus_index < audio_expanded_views.size()) { + audio_expanded_views[bus_index]->gainstaging_auto_checkbox->setCheckState(Qt::Unchecked); + } float gain_db = value * 0.1f; - global_mixer->get_audio_mixer()->set_gain_staging_db(gain_db); + global_mixer->get_audio_mixer()->set_gain_staging_db(bus_index, gain_db); // The label will be updated by the audio level callback. } @@ -465,12 +493,18 @@ void MainWindow::limiter_threshold_knob_changed(int value) QString::fromStdString(format_db(threshold_dbfs, DB_WITH_SIGN))); } -void MainWindow::compressor_threshold_knob_changed(int value) +void MainWindow::compressor_threshold_knob_changed(unsigned bus_index, int value) { float threshold_dbfs = value * 0.1f; - global_mixer->get_audio_mixer()->set_compressor_threshold_dbfs(threshold_dbfs); - ui->compressor_threshold_db_display->setText( - QString::fromStdString(format_db(threshold_dbfs, DB_WITH_SIGN))); + global_mixer->get_audio_mixer()->set_compressor_threshold_dbfs(bus_index, threshold_dbfs); + + QString label(QString::fromStdString(format_db(threshold_dbfs, DB_WITH_SIGN))); + if (bus_index == 0) { + ui->compressor_threshold_db_display->setText(label); + } + if (bus_index < audio_expanded_views.size()) { + audio_expanded_views[bus_index]->compressor_threshold_db_display->setText(label); + } } void MainWindow::mini_fader_changed(int bus, double volume_db) @@ -492,7 +526,7 @@ void MainWindow::reset_meters_button_clicked() void MainWindow::audio_level_callback(float level_lufs, float peak_db, vector bus_level_lufs, float global_level_lufs, float range_low_lufs, float range_high_lufs, - float gain_staging_db, float final_makeup_gain_db, + vector gain_staging_db, float final_makeup_gain_db, float correlation) { steady_clock::time_point now = steady_clock::now(); @@ -511,6 +545,15 @@ void MainWindow::audio_level_callback(float level_lufs, float peak_db, vectorvu_meter_meter->set_level( bus_level_lufs[bus_index]); + + Ui::AudioExpandedView *view = audio_expanded_views[bus_index]; + view->vu_meter_meter->set_level(bus_level_lufs[bus_index]); + view->gainstaging_knob->blockSignals(true); + view->gainstaging_knob->setValue(lrintf(gain_staging_db[bus_index] * 10.0f)); + view->gainstaging_knob->blockSignals(false); + view->gainstaging_db_display->setText( + QString("Gain: ") + + QString::fromStdString(format_db(gain_staging_db[bus_index], DB_WITH_SIGN))); } } ui->lra_meter->set_levels(global_level_lufs, range_low_lufs, range_high_lufs); @@ -523,11 +566,12 @@ void MainWindow::audio_level_callback(float level_lufs, float peak_db, vectorpeak_display->setStyleSheet(""); } + // NOTE: Will be invisible when using multitrack audio. ui->gainstaging_knob->blockSignals(true); - ui->gainstaging_knob->setValue(lrintf(gain_staging_db * 10.0f)); + ui->gainstaging_knob->setValue(lrintf(gain_staging_db[0] * 10.0f)); ui->gainstaging_knob->blockSignals(false); ui->gainstaging_db_display->setText( - QString::fromStdString(format_db(gain_staging_db, DB_WITH_SIGN))); + QString::fromStdString(format_db(gain_staging_db[0], DB_WITH_SIGN))); ui->makeup_gain_knob->blockSignals(true); ui->makeup_gain_knob->setValue(lrintf(final_makeup_gain_db * 10.0f)); diff --git a/mainwindow.h b/mainwindow.h index ff33b7f..41c9fe7 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -47,11 +47,11 @@ public slots: void set_transition_names(std::vector transition_names); void update_channel_name(Mixer::Output output, const std::string &name); void update_channel_color(Mixer::Output output, const std::string &color); - void gain_staging_knob_changed(int value); + void gain_staging_knob_changed(unsigned bus_index, int value); void final_makeup_gain_knob_changed(int value); void cutoff_knob_changed(int value); void limiter_threshold_knob_changed(int value); - void compressor_threshold_knob_changed(int value); + void compressor_threshold_knob_changed(unsigned bus_index, int value); void mini_fader_changed(int bus, double db_volume); void reset_meters_button_clicked(); void relayout(); @@ -66,7 +66,7 @@ private: void report_disk_space(off_t free_bytes, double estimated_seconds_left); // Called from the mixer. - void audio_level_callback(float level_lufs, float peak_db, std::vector bus_level_lufs, float global_level_lufs, float range_low_lufs, float range_high_lufs, float gain_staging_db, float final_makeup_gain_db, float correlation); + void audio_level_callback(float level_lufs, float peak_db, std::vector bus_level_lufs, float global_level_lufs, float range_low_lufs, float range_high_lufs, std::vector gain_staging_db, float final_makeup_gain_db, float correlation); std::chrono::steady_clock::time_point last_audio_level_callback; Ui::MainWindow *ui; -- 2.39.2