]> git.sesse.net Git - nageru/commitdiff
Add support for stereo width on the audio bus.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 30 Apr 2018 23:11:23 +0000 (01:11 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 30 Apr 2018 23:11:23 +0000 (01:11 +0200)
audio_mixer.cpp
audio_mixer.h
mainwindow.cpp
mainwindow.h
midi_mapper.cpp
midi_mapper.h
midi_mapping.proto
midi_mapping_dialog.cpp
midi_mapping_dialog.h
ui_audio_expanded_view.ui

index e7d545f69c6320c25cddcde039f3b100d8332529..6f6190a433f20d0b84bc2f85239cbc50ae354bd1 100644 (file)
@@ -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<DeviceSpec, vector<float>
 }
 
 // TODO: Can be SSSE3-optimized if need be.
-void AudioMixer::fill_audio_bus(const map<DeviceSpec, vector<float>> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float *output)
+void AudioMixer::fill_audio_bus(const map<DeviceSpec, vector<float>> &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<DeviceSpec, vector<float>> &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<float> 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);
 
                {
index ebe142a74cbc9e6f81d125505022d88588cb00cd..919cef01e483672957db421f871ab65cf10d8a30 100644 (file)
@@ -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<DeviceSpec, std::vector<float>> &samples_card, DeviceSpec device_spec, int source_channel, const float **srcptr, unsigned *stride);
-       void fill_audio_bus(const std::map<DeviceSpec, std::vector<float>> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float *output);
+       void fill_audio_bus(const std::map<DeviceSpec, std::vector<float>> &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<float> *samples_bus);
        void update_meters(const std::vector<float> &samples);
@@ -376,6 +387,7 @@ private:
        std::atomic<float> fader_volume_db[MAX_BUSES] {{ 0.0f }};
        std::atomic<bool> mute[MAX_BUSES] {{ false }};
        float last_fader_volume_db[MAX_BUSES] { 0.0f };  // Under audio_mutex.
+       std::atomic<float> stereo_width[MAX_BUSES] {{ 0.0f }};  // Default 1.0f (is set in constructor).
        std::atomic<float> eq_level_db[MAX_BUSES][NUM_EQ_BANDS] {{{ 0.0f }}};
        float last_eq_level_db[MAX_BUSES][NUM_EQ_BANDS] {{ 0.0f }};
 
index 839f1b359e1a9060fdb64a8cfdd942b0f42c26ea..ead7a872422c9d676ff66fa210e7324dde7b3511 100644 (file)
@@ -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);
index 8b896890cc1aaaa73c71e29c3f8c0df6a2fcec24..ea0fcdfa15446c02aa26e46dc82d037a209a2517 100644 (file)
@@ -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();
 
index 42ad1e5e2a00f3e29d43459e77b41ff1b90fbe33..3b22192b3b8b0670356ebdb9230a8133777b0983 100644 (file)
@@ -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(
index 04e5192ecb0d69a432cc159b83553f0827d37e75..ac3578b1a98c32e9f4e6969c73e14af00ddab854 100644 (file)
@@ -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;
index 36bfe8d444aad8e2b7e7b5a9bbd4d81548da7359..4a8b8521985756b9172880707b7f544aa487703d 100644 (file)
@@ -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;
index 89656039698b3fe47d36a4e26f7a73767f876d09..cc9f394a78bdc63b41f156eea7d1050d361e694c 100644 (file)
@@ -29,6 +29,8 @@ using namespace google::protobuf;
 using namespace std;
 
 vector<MIDIMappingDialog::Control> per_bus_controllers = {
+       { "Stereo width",             MIDIMappingBusProto::kStereoWidthFieldNumber,
+                                     MIDIMappingProto::kStereoWidthBankFieldNumber },
        { "Treble",                   MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber },
        { "Mid",                      MIDIMappingBusProto::kMidFieldNumber,    MIDIMappingProto::kMidBankFieldNumber },
        { "Bass",                     MIDIMappingBusProto::kBassFieldNumber,   MIDIMappingProto::kBassBankFieldNumber },
index 5a1ec3f425a9b4caada9a8fe768e1cb26717636c..c36781d1bb6af0521ba5b06e11a29edd1e32ca2d 100644 (file)
@@ -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 {}
index 3c560d4ed6b497f9225ca3031e0e1dc87239629a..e71e153d85fcedfb40cc9f024e20d85fb56ddc73 100644 (file)
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>312</width>
-    <height>434</height>
+    <height>484</height>
    </rect>
   </property>
   <property name="windowTitle">
          </property>
         </spacer>
        </item>
+       <item>
+        <layout class="QHBoxLayout" name="stereo_width_layout">
+         <item>
+          <widget class="QDial" name="stereo_width_knob">
+           <property name="maximumSize">
+            <size>
+             <width>31</width>
+             <height>31</height>
+            </size>
+           </property>
+           <property name="minimum">
+            <number>-100</number>
+           </property>
+           <property name="maximum">
+            <number>100</number>
+           </property>
+           <property name="value">
+            <number>100</number>
+           </property>
+           <property name="notchTarget">
+            <double>50.000000000000000</double>
+           </property>
+           <property name="notchesVisible">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="stereo_width_label">
+           <property name="text">
+            <string>Stereo: 100%</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="Line" name="line_3">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
        <item>
         <layout class="QHBoxLayout" name="locut_offseter">
          <property name="leftMargin">