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;
}
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];
}
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];
}
}
// 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));
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;
+ }
}
}
}
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);
{
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);
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;
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);
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 }};
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));
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,
});
}
+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;
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];
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);
});
}
+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);
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);
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;
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;
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();
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,
// 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(
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;
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;
// 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;
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;
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 },
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 {}
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 {}
<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">