From c6ae618df75df74072f586d9df03610b4a6d353d Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 1 May 2018 01:11:23 +0200 Subject: [PATCH] Add support for stereo width on the audio bus. --- audio_mixer.cpp | 50 +++++++++++++++++++++++++++++++++------ audio_mixer.h | 14 ++++++++++- mainwindow.cpp | 31 ++++++++++++++++++++++++ mainwindow.h | 4 ++++ midi_mapper.cpp | 4 ++++ midi_mapper.h | 2 ++ midi_mapping.proto | 2 ++ midi_mapping_dialog.cpp | 2 ++ midi_mapping_dialog.h | 2 ++ ui_audio_expanded_view.ui | 45 ++++++++++++++++++++++++++++++++++- 10 files changed, 147 insertions(+), 9 deletions(-) diff --git a/audio_mixer.cpp b/audio_mixer.cpp index e7d545f..6f6190a 100644 --- a/audio_mixer.cpp +++ b/audio_mixer.cpp @@ -339,6 +339,7 @@ AudioMixer::BusSettings AudioMixer::get_default_bus_settings() settings.fader_volume_db = 0.0f; settings.muted = false; settings.locut_enabled = global_flags.locut_enabled; + settings.stereo_width = 1.0f; for (unsigned band_index = 0; band_index < NUM_EQ_BANDS; ++band_index) { settings.eq_level_db[band_index] = 0.0f; } @@ -356,6 +357,7 @@ AudioMixer::BusSettings AudioMixer::get_bus_settings(unsigned bus_index) const settings.fader_volume_db = fader_volume_db[bus_index]; settings.muted = mute[bus_index]; settings.locut_enabled = locut_enabled[bus_index]; + settings.stereo_width = stereo_width[bus_index]; for (unsigned band_index = 0; band_index < NUM_EQ_BANDS; ++band_index) { settings.eq_level_db[band_index] = eq_level_db[bus_index][band_index]; } @@ -372,6 +374,7 @@ void AudioMixer::set_bus_settings(unsigned bus_index, const AudioMixer::BusSetti fader_volume_db[bus_index] = settings.fader_volume_db; mute[bus_index] = settings.muted; locut_enabled[bus_index] = settings.locut_enabled; + stereo_width[bus_index] = settings.stereo_width; for (unsigned band_index = 0; band_index < NUM_EQ_BANDS; ++band_index) { eq_level_db[bus_index][band_index] = settings.eq_level_db[band_index]; } @@ -423,7 +426,7 @@ void AudioMixer::find_sample_src_from_device(const map } // TODO: Can be SSSE3-optimized if need be. -void AudioMixer::fill_audio_bus(const map> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float *output) +void AudioMixer::fill_audio_bus(const map> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float stereo_width, float *output) { if (bus.device.type == InputSourceType::SILENCE) { memset(output, 0, num_samples * 2 * sizeof(*output)); @@ -436,11 +439,44 @@ void AudioMixer::fill_audio_bus(const map> &samples_ca float *dptr = output; find_sample_src_from_device(samples_card, bus.device, bus.source_channel[0], &lsrc, &lstride); find_sample_src_from_device(samples_card, bus.device, bus.source_channel[1], &rsrc, &rstride); - for (unsigned i = 0; i < num_samples; ++i) { - *dptr++ = *lsrc; - *dptr++ = *rsrc; - lsrc += lstride; - rsrc += rstride; + + // Apply stereo width settings. Set stereo width w to a 0..1 range instead of + // -1..1, since it makes for much easier calculations (so 0.5 = completely mono). + // Then, what we want is + // + // L' = wL + (1-w)R = R + w(L-R) + // R' = wR + (1-w)L = L + w(R-L) + // + // This can be further simplified calculation-wise by defining the weighted + // difference signal D = w(R-L), so that: + // + // L' = R - D + // R' = L + D + float w = 0.5f * stereo_width + 0.5f; + if (fabs(w) < 1e-3) { + // Perfect inverse. + swap(lsrc, rsrc); + swap(lstride, rstride); + w = 1.0f; + } + if (fabs(w - 1.0f) < 1e-3) { + // No calculations needed for stereo_width = 1. + for (unsigned i = 0; i < num_samples; ++i) { + *dptr++ = *lsrc; + *dptr++ = *rsrc; + lsrc += lstride; + rsrc += rstride; + } + } else { + // General case. + for (unsigned i = 0; i < num_samples; ++i) { + float left = *lsrc, right = *rsrc; + float diff = w * (right - left); + *dptr++ = right - diff; + *dptr++ = left + diff; + lsrc += lstride; + rsrc += rstride; + } } } } @@ -520,7 +556,7 @@ vector AudioMixer::get_output(steady_clock::time_point ts, unsigned num_s samples_out.resize(num_samples * 2); 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]); + fill_audio_bus(samples_card, input_mapping.buses[bus_index], num_samples, stereo_width[bus_index], &samples_bus[0]); apply_eq(bus_index, &samples_bus); { diff --git a/audio_mixer.h b/audio_mixer.h index ebe142a..919cef0 100644 --- a/audio_mixer.h +++ b/audio_mixer.h @@ -142,6 +142,16 @@ public: return locut_enabled[bus]; } + void set_stereo_width(unsigned bus_index, float width) + { + stereo_width[bus_index] = width; + } + + float get_stereo_width(unsigned bus_index) + { + return stereo_width[bus_index]; + } + void set_eq(unsigned bus_index, EQBand band, float db_gain) { assert(band >= 0 && band < NUM_EQ_BANDS); @@ -289,6 +299,7 @@ public: float fader_volume_db; bool muted; bool locut_enabled; + float stereo_width; float eq_level_db[NUM_EQ_BANDS]; float gain_staging_db; bool level_compressor_enabled; @@ -317,7 +328,7 @@ private: AudioDevice *find_audio_device(DeviceSpec device_spec); void find_sample_src_from_device(const std::map> &samples_card, DeviceSpec device_spec, int source_channel, const float **srcptr, unsigned *stride); - void fill_audio_bus(const std::map> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float *output); + void fill_audio_bus(const std::map> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float stereo_width, float *output); void reset_resampler_mutex_held(DeviceSpec device_spec); void apply_eq(unsigned bus_index, std::vector *samples_bus); void update_meters(const std::vector &samples); @@ -376,6 +387,7 @@ private: std::atomic fader_volume_db[MAX_BUSES] {{ 0.0f }}; std::atomic mute[MAX_BUSES] {{ false }}; float last_fader_volume_db[MAX_BUSES] { 0.0f }; // Under audio_mutex. + std::atomic stereo_width[MAX_BUSES] {{ 0.0f }}; // Default 1.0f (is set in constructor). std::atomic eq_level_db[MAX_BUSES][NUM_EQ_BANDS] {{{ 0.0f }}}; float last_eq_level_db[MAX_BUSES][NUM_EQ_BANDS] {{ 0.0f }}; diff --git a/mainwindow.cpp b/mainwindow.cpp index 839f1b3..ead7a87 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -557,6 +557,7 @@ void MainWindow::setup_audio_expanded_view() ui_audio_expanded_view->bus_desc_label->setFullText( QString::fromStdString(get_bus_desc_label(mapping.buses[bus_index]))); audio_expanded_views[bus_index] = ui_audio_expanded_view; + update_stereo_label(bus_index, lrintf(100.0f * global_audio_mixer->get_stereo_width(bus_index))); update_eq_label(bus_index, EQ_BAND_TREBLE, global_audio_mixer->get_eq(bus_index, EQ_BAND_TREBLE)); update_eq_label(bus_index, EQ_BAND_MID, global_audio_mixer->get_eq(bus_index, EQ_BAND_MID)); update_eq_label(bus_index, EQ_BAND_BASS, global_audio_mixer->get_eq(bus_index, EQ_BAND_BASS)); @@ -572,6 +573,9 @@ void MainWindow::setup_audio_expanded_view() midi_mapper.refresh_lights(); }); + connect(ui_audio_expanded_view->stereo_width_knob, &QDial::valueChanged, + bind(&MainWindow::stereo_width_knob_changed, this, bus_index, _1)); + connect(ui_audio_expanded_view->treble_knob, &QDial::valueChanged, bind(&MainWindow::eq_knob_changed, this, bus_index, EQ_BAND_TREBLE, _1)); connect(ui_audio_expanded_view->mid_knob, &QDial::valueChanged, @@ -805,6 +809,14 @@ void MainWindow::report_disk_space(off_t free_bytes, double estimated_seconds_le }); } +void MainWindow::stereo_width_knob_changed(unsigned bus_index, int value) +{ + float stereo_width = value * 0.01f; + global_audio_mixer->set_stereo_width(bus_index, stereo_width); + + update_stereo_label(bus_index, value); +} + void MainWindow::eq_knob_changed(unsigned bus_index, EQBand band, int value) { float gain_db = value * 0.1f; @@ -813,6 +825,15 @@ void MainWindow::eq_knob_changed(unsigned bus_index, EQBand band, int value) update_eq_label(bus_index, band, gain_db); } +void MainWindow::update_stereo_label(unsigned bus_index, int stereo_width_percent) +{ + char buf[256]; + snprintf(buf, sizeof(buf), "Stereo: %d%%", stereo_width_percent); + + Ui::AudioExpandedView *view = audio_expanded_views[bus_index]; + view->stereo_width_label->setText(buf); +} + void MainWindow::update_eq_label(unsigned bus_index, EQBand band, float gain_db) { Ui::AudioExpandedView *view = audio_expanded_views[bus_index]; @@ -1098,6 +1119,11 @@ void MainWindow::set_makeup_gain(float value) set_relative_value(ui->makeup_gain_knob, value); } +void MainWindow::set_stereo_width(unsigned bus_idx, float value) +{ + set_relative_value_if_exists(bus_idx, &Ui::AudioExpandedView::stereo_width_knob, value); +} + void MainWindow::set_treble(unsigned bus_idx, float value) { set_relative_value_if_exists(bus_idx, &Ui::AudioExpandedView::treble_knob, value); @@ -1220,6 +1246,11 @@ void MainWindow::highlight_makeup_gain(bool highlight) }); } +void MainWindow::highlight_stereo_width(unsigned bus_idx, bool highlight) +{ + highlight_control_if_exists(bus_idx, &Ui::AudioExpandedView::stereo_width_knob, highlight); +} + void MainWindow::highlight_treble(unsigned bus_idx, bool highlight) { highlight_control_if_exists(bus_idx, &Ui::AudioExpandedView::treble_knob, highlight); diff --git a/mainwindow.h b/mainwindow.h index 8b89689..ea0fcdf 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -66,6 +66,7 @@ public slots: 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 stereo_width_knob_changed(unsigned bus_index, int value); void eq_knob_changed(unsigned bus_index, EQBand band, int value); void limiter_threshold_knob_changed(int value); void compressor_threshold_knob_changed(unsigned bus_index, int value); @@ -79,6 +80,7 @@ public slots: void set_limiter_threshold(float value) override; void set_makeup_gain(float value) override; + void set_stereo_width(unsigned bus_idx, float value) override; void set_treble(unsigned bus_idx, float value) override; void set_mid(unsigned bus_idx, float value) override; void set_bass(unsigned bus_idx, float value) override; @@ -100,6 +102,7 @@ public slots: void highlight_limiter_threshold(bool highlight) override; void highlight_makeup_gain(bool highlight) override; + void highlight_stereo_width(unsigned bus_idx, bool highlight) override; void highlight_treble(unsigned bus_idx, bool highlight) override; void highlight_mid(unsigned bus_idx, bool highlight) override; void highlight_bass(unsigned bus_idx, bool highlight) override; @@ -126,6 +129,7 @@ private: bool eventFilter(QObject *watched, QEvent *event) override; void closeEvent(QCloseEvent *event) override; void update_cutoff_labels(float cutoff_hz); + void update_stereo_label(unsigned bus_index, int stereo_width_percent); void update_eq_label(unsigned bus_index, EQBand band, float gain_db); void setup_theme_menu(); diff --git a/midi_mapper.cpp b/midi_mapper.cpp index 42ad1e5..3b22192 100644 --- a/midi_mapper.cpp +++ b/midi_mapper.cpp @@ -270,6 +270,8 @@ void MIDIMapper::handle_event(snd_seq_t *seq, snd_seq_event_t *event) value, bind(&ControllerReceiver::set_makeup_gain, receiver, _2)); // Bus controllers. + match_controller(controller, MIDIMappingBusProto::kStereoWidthFieldNumber, MIDIMappingProto::kStereoWidthBankFieldNumber, + value, bind(&ControllerReceiver::set_stereo_width, receiver, _1, _2)); match_controller(controller, MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber, value, bind(&ControllerReceiver::set_treble, receiver, _1, _2)); match_controller(controller, MIDIMappingBusProto::kMidFieldNumber, MIDIMappingProto::kMidBankFieldNumber, @@ -543,6 +545,8 @@ void MIDIMapper::update_highlights() // Per-bus controllers. for (size_t bus_idx = 0; bus_idx < size_t(mapping_proto->bus_mapping_size()); ++bus_idx) { + receiver->highlight_stereo_width(bus_idx, has_active_controller( + bus_idx, MIDIMappingBusProto::kStereoWidthFieldNumber, MIDIMappingProto::kStereoWidthBankFieldNumber)); receiver->highlight_treble(bus_idx, has_active_controller( bus_idx, MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber)); receiver->highlight_mid(bus_idx, has_active_controller( diff --git a/midi_mapper.h b/midi_mapper.h index 04e5192..ac3578b 100644 --- a/midi_mapper.h +++ b/midi_mapper.h @@ -32,6 +32,7 @@ public: virtual void set_limiter_threshold(float value) = 0; virtual void set_makeup_gain(float value) = 0; + virtual void set_stereo_width(unsigned bus_idx, float value) = 0; virtual void set_treble(unsigned bus_idx, float value) = 0; virtual void set_mid(unsigned bus_idx, float value) = 0; virtual void set_bass(unsigned bus_idx, float value) = 0; @@ -55,6 +56,7 @@ public: virtual void highlight_limiter_threshold(bool highlight) = 0; virtual void highlight_makeup_gain(bool highlight) = 0; + virtual void highlight_stereo_width(unsigned bus_idx, bool highlight) = 0; virtual void highlight_treble(unsigned bus_idx, bool highlight) = 0; virtual void highlight_mid(unsigned bus_idx, bool highlight) = 0; virtual void highlight_bass(unsigned bus_idx, bool highlight) = 0; diff --git a/midi_mapping.proto b/midi_mapping.proto index 36bfe8d..4a8b852 100644 --- a/midi_mapping.proto +++ b/midi_mapping.proto @@ -24,6 +24,7 @@ message MIDIMappingBusProto { // on a mixer), add a system for bus banks, like we have for controller banks. // optional int32 bus_bank = 1; + optional MIDIControllerProto stereo_width = 37; optional MIDIControllerProto treble = 2; optional MIDIControllerProto mid = 3; optional MIDIControllerProto bass = 4; @@ -90,6 +91,7 @@ message MIDIMappingProto { optional int32 num_controller_banks = 1 [default = 0]; // Max 5. // Bus controller banks. + optional int32 stereo_width_bank = 19; optional int32 treble_bank = 2; optional int32 mid_bank = 3; optional int32 bass_bank = 4; diff --git a/midi_mapping_dialog.cpp b/midi_mapping_dialog.cpp index 8965603..cc9f394 100644 --- a/midi_mapping_dialog.cpp +++ b/midi_mapping_dialog.cpp @@ -29,6 +29,8 @@ using namespace google::protobuf; using namespace std; vector per_bus_controllers = { + { "Stereo width", MIDIMappingBusProto::kStereoWidthFieldNumber, + MIDIMappingProto::kStereoWidthBankFieldNumber }, { "Treble", MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber }, { "Mid", MIDIMappingBusProto::kMidFieldNumber, MIDIMappingProto::kMidBankFieldNumber }, { "Bass", MIDIMappingBusProto::kBassFieldNumber, MIDIMappingProto::kBassBankFieldNumber }, diff --git a/midi_mapping_dialog.h b/midi_mapping_dialog.h index 5a1ec3f..c36781d 100644 --- a/midi_mapping_dialog.h +++ b/midi_mapping_dialog.h @@ -47,6 +47,7 @@ public: void set_limiter_threshold(float value) override {} void set_makeup_gain(float value) override {} + void set_stereo_width(unsigned bus_idx, float value) override {} void set_treble(unsigned bus_idx, float value) override {} void set_mid(unsigned bus_idx, float value) override {} void set_bass(unsigned bus_idx, float value) override {} @@ -68,6 +69,7 @@ public: void highlight_limiter_threshold(bool highlight) override {} void highlight_makeup_gain(bool highlight) override {} + void highlight_stereo_width(unsigned bus_idx, bool highlight) override {} void highlight_treble(unsigned bus_idx, bool highlight) override {} void highlight_mid(unsigned bus_idx, bool highlight) override {} void highlight_bass(unsigned bus_idx, bool highlight) override {} diff --git a/ui_audio_expanded_view.ui b/ui_audio_expanded_view.ui index 3c560d4..e71e153 100644 --- a/ui_audio_expanded_view.ui +++ b/ui_audio_expanded_view.ui @@ -7,7 +7,7 @@ 0 0 312 - 434 + 484 @@ -49,6 +49,49 @@ + + + + + + + 31 + 31 + + + + -100 + + + 100 + + + 100 + + + 50.000000000000000 + + + true + + + + + + + Stereo: 100% + + + + + + + + + Qt::Horizontal + + + -- 2.39.2