#include <stdio.h>
#include <endian.h>
#include <cmath>
+#include <limits>
#ifdef __SSE__
#include <immintrin.h>
#endif
set_final_makeup_gain_auto(global_flags.final_makeup_gain_auto);
alsa_pool.init();
- InputMapping new_input_mapping;
if (!global_flags.input_mapping_filename.empty()) {
+ current_mapping_mode = MappingMode::MULTICHANNEL;
+ InputMapping new_input_mapping;
if (!load_input_mapping_from_file(get_devices(),
global_flags.input_mapping_filename,
&new_input_mapping)) {
global_flags.input_mapping_filename.c_str());
exit(1);
}
+ set_input_mapping(new_input_mapping);
} else {
- // Generate a very simple, default input mapping.
- InputMapping::Bus input;
- input.name = "Main";
- input.device.type = InputSourceType::CAPTURE_CARD;
- input.device.index = 0;
- input.source_channel[0] = 0;
- input.source_channel[1] = 1;
-
- new_input_mapping.buses.push_back(input);
+ set_simple_input(/*card_index=*/0);
+ if (global_flags.multichannel_mapping_mode) {
+ current_mapping_mode = MappingMode::MULTICHANNEL;
+ }
}
- set_input_mapping(new_input_mapping);
r128.init(2, OUTPUT_FREQUENCY);
r128.integr_start();
}
}
+void AudioMixer::set_simple_input(unsigned card_index)
+{
+ InputMapping new_input_mapping;
+ InputMapping::Bus input;
+ input.name = "Main";
+ input.device.type = InputSourceType::CAPTURE_CARD;
+ input.device.index = card_index;
+ input.source_channel[0] = 0;
+ input.source_channel[1] = 1;
+
+ new_input_mapping.buses.push_back(input);
+
+ lock_guard<timed_mutex> lock(audio_mutex);
+ current_mapping_mode = MappingMode::SIMPLE;
+ set_input_mapping_lock_held(new_input_mapping);
+ fader_volume_db[0] = 0.0f;
+}
+
+unsigned AudioMixer::get_simple_input() const
+{
+ lock_guard<timed_mutex> lock(audio_mutex);
+ if (input_mapping.buses.size() == 1 &&
+ input_mapping.buses[0].device.type == InputSourceType::CAPTURE_CARD &&
+ input_mapping.buses[0].source_channel[0] == 0 &&
+ input_mapping.buses[0].source_channel[1] == 1) {
+ return input_mapping.buses[0].device.index;
+ } else {
+ return numeric_limits<unsigned>::max();
+ }
+}
+
void AudioMixer::set_input_mapping(const InputMapping &new_input_mapping)
{
lock_guard<timed_mutex> lock(audio_mutex);
+ set_input_mapping_lock_held(new_input_mapping);
+ current_mapping_mode = MappingMode::MULTICHANNEL;
+}
+AudioMixer::MappingMode AudioMixer::get_mapping_mode() const
+{
+ lock_guard<timed_mutex> lock(audio_mutex);
+ return current_mapping_mode;
+}
+
+void AudioMixer::set_input_mapping_lock_held(const InputMapping &new_input_mapping)
+{
map<DeviceSpec, set<unsigned>> interesting_channels;
for (const InputMapping::Bus &bus : new_input_mapping.buses) {
if (bus.device.type == InputSourceType::CAPTURE_CARD ||
// Note: The card should be held (currently this isn't enforced, though).
void serialize_device(DeviceSpec device_spec, DeviceSpecProto *device_spec_proto);
+ enum class MappingMode {
+ // A single bus, only from a video card (no ALSA devices),
+ // only channel 1 and 2, locked to +0 dB. Note that this is
+ // only an UI abstraction around exactly the same audio code
+ // as MULTICHANNEL; it's just less flexible.
+ SIMPLE,
+
+ // Full, arbitrary mappings.
+ MULTICHANNEL
+ };
+
+ // Automatically sets mapping mode to MappingMode::SIMPLE.
+ void set_simple_input(unsigned card_index);
+
+ // If mapping mode is not representable as a MappingMode::SIMPLE type
+ // mapping, returns numeric_limits<unsigned>::max().
+ unsigned get_simple_input() const;
+
+ // Implicitly sets mapping mode to MappingMode::MULTICHANNEL.
void set_input_mapping(const InputMapping &input_mapping);
+
+ MappingMode get_mapping_mode() const;
InputMapping get_input_mapping() const;
void set_locut_cutoff(float cutoff_hz)
void measure_bus_levels(unsigned bus_index, const std::vector<float> &left, const std::vector<float> &right);
void send_audio_level_callback();
std::vector<DeviceSpec> get_active_devices() const;
+ void set_input_mapping_lock_held(const InputMapping &input_mapping);
unsigned num_cards;
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.
+ MappingMode current_mapping_mode; // Under audio_mutex.
InputMapping input_mapping; // Under audio_mutex.
std::atomic<float> fader_volume_db[MAX_BUSES] {{ 0.0f }};
float last_fader_volume_db[MAX_BUSES] { 0.0f }; // Under audio_mutex.
// Long options that have no corresponding short option.
enum LongOption {
- OPTION_FAKE_CARDS_AUDIO = 1000,
+ OPTION_MULTICHANNEL = 1000,
+ OPTION_FAKE_CARDS_AUDIO,
OPTION_HTTP_UNCOMPRESSED_VIDEO,
OPTION_HTTP_X264_VIDEO,
OPTION_X264_PRESET,
fprintf(stderr, " -v, --va-display=SPEC VA-API device for H.264 encoding\n");
fprintf(stderr, " ($DISPLAY spec or /dev/dri/render* path)\n");
fprintf(stderr, " -m, --map-signal=SIGNAL,CARD set a default card mapping (can be given multiple times)\n");
- fprintf(stderr, " -M, --input-mapping=FILE start with the given audio input mapping\n");
+ fprintf(stderr, " -M, --input-mapping=FILE start with the given audio input mapping (implies --multichannel)\n");
+ fprintf(stderr, " --multichannel start in multichannel audio mapping mode\n");
fprintf(stderr, " --fake-cards-audio make fake (disconnected) cards output a simple tone\n");
fprintf(stderr, " --http-uncompressed-video send uncompressed NV12 video to HTTP clients\n");
fprintf(stderr, " --http-x264-video send x264-compressed video to HTTP clients\n");
{ "map-signal", required_argument, 0, 'm' },
{ "input-mapping", required_argument, 0, 'M' },
{ "va-display", required_argument, 0, 'v' },
+ { "multichannel", no_argument, 0, OPTION_MULTICHANNEL },
{ "fake-cards-audio", no_argument, 0, OPTION_FAKE_CARDS_AUDIO },
{ "http-uncompressed-video", no_argument, 0, OPTION_HTTP_UNCOMPRESSED_VIDEO },
{ "http-x264-video", no_argument, 0, OPTION_HTTP_X264_VIDEO },
case 'M':
global_flags.input_mapping_filename = optarg;
break;
+ case OPTION_MULTICHANNEL:
+ global_flags.multichannel_mapping_mode = true;
+ break;
case 'v':
global_flags.va_display = optarg;
break;
std::vector<std::string> x264_extra_param; // In “key[,value]” format.
bool enable_alsa_output = true;
std::map<int, int> default_stream_mapping;
+ bool multichannel_mapping_mode = false; // Implicitly true if input_mapping_filename is nonempty.
std::string input_mapping_filename; // Empty for none.
};
extern Flags global_flags;
#include <stdio.h>
#include <functional>
+#include <limits>
#include <mutex>
#include <movit/effect_chain.h>
#include <movit/resource_pool.h>
// --- End of card-dependent choices ---
// Add an audio source selector.
- QAction *audio_source_action = new QAction("Use as audio source", &menu);
- audio_source_action->setCheckable(true);
- if (global_mixer->get_audio_source() == signal_num) {
- audio_source_action->setChecked(true);
- audio_source_action->setEnabled(false);
+ QAction *audio_source_action = nullptr;
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) {
+ audio_source_action = new QAction("Use as audio source", &menu);
+ audio_source_action->setCheckable(true);
+ if (global_audio_mixer->get_simple_input() == signal_num) {
+ audio_source_action->setChecked(true);
+ audio_source_action->setEnabled(false);
+ }
+ menu.addAction(audio_source_action);
}
- menu.addAction(audio_source_action);
// And a master clock selector.
QAction *master_clock_action = new QAction("Use as master clock", &menu);
// Show the menu and look at the result.
QAction *selected_item = menu.exec(global_pos);
- if (selected_item == audio_source_action) {
- global_mixer->set_audio_source(signal_num);
+ if (audio_source_action != nullptr && selected_item == audio_source_action) {
+ global_audio_mixer->set_simple_input(signal_num);
} else if (selected_item == master_clock_action) {
global_mixer->set_master_clock(signal_num);
} else if (selected_item != nullptr) {
#include <QInputDialog>
#include <QKeySequence>
#include <QLabel>
+#include <QMessageBox>
#include <QMetaType>
#include <QPushButton>
#include <QResizeEvent>
disk_free_label->setStyleSheet("QLabel {padding-right: 5px;}");
ui->menuBar->setCornerWidget(disk_free_label);
+ QActionGroup *audio_mapping_group = new QActionGroup(this);
+ ui->simple_audio_mode->setActionGroup(audio_mapping_group);
+ ui->multichannel_audio_mode->setActionGroup(audio_mapping_group);
+
ui->me_live->set_output(Mixer::OUTPUT_LIVE);
ui->me_preview->set_output(Mixer::OUTPUT_PREVIEW);
connect(ui->cut_action, &QAction::triggered, this, &MainWindow::cut_triggered);
connect(ui->exit_action, &QAction::triggered, this, &MainWindow::exit_triggered);
connect(ui->about_action, &QAction::triggered, this, &MainWindow::about_triggered);
+ connect(ui->simple_audio_mode, &QAction::triggered, this, &MainWindow::simple_audio_mode_triggered);
+ connect(ui->multichannel_audio_mode, &QAction::triggered, this, &MainWindow::multichannel_audio_mode_triggered);
connect(ui->input_mapping_action, &QAction::triggered, this, &MainWindow::input_mapping_triggered);
if (global_flags.x264_video_to_http) {
// And bind the same to PgUp/PgDown.
auto switch_page = [this]{
- ui->audio_views->setCurrentIndex(1 - ui->audio_views->currentIndex());
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL) {
+ ui->audio_views->setCurrentIndex(1 - ui->audio_views->currentIndex());
+ }
};
connect(new QShortcut(QKeySequence::MoveToNextPage, this), &QShortcut::activated, switch_page);
connect(new QShortcut(QKeySequence::MoveToPreviousPage, this), &QShortcut::activated, switch_page);
connect(ui_display->wb_button, &QPushButton::clicked, bind(&MainWindow::wb_button_clicked, this, i));
}
- setup_audio_miniview();
- setup_audio_expanded_view();
global_audio_mixer->set_state_changed_callback(bind(&MainWindow::audio_state_changed, this));
slave_knob(ui->locut_cutoff_knob, ui->locut_cutoff_knob_2);
slave_checkbox(ui->makeup_gain_auto_checkbox, ui->makeup_gain_auto_checkbox_2);
slave_checkbox(ui->limiter_enabled, ui->limiter_enabled_2);
+ reset_audio_mapping_ui();
+
// TODO: Fetch all of the values these for completeness,
// not just the enable knobs implied by flags.
-#if 0
- // TODO: Reenable for simple audio.
- ui->locut_enabled->setChecked(global_audio_mixer->get_locut_enabled(0));
+ ui->limiter_enabled->setChecked(global_audio_mixer->get_limiter_enabled());
+ ui->makeup_gain_auto_checkbox->setChecked(global_audio_mixer->get_final_makeup_gain_auto());
+
+ // Controls used only for simple audio fetch their state from the first bus.
+ constexpr unsigned simple_bus_index = 0;
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) {
+ ui->locut_enabled->setChecked(global_audio_mixer->get_locut_enabled(simple_bus_index));
+ ui->gainstaging_knob->setValue(global_audio_mixer->get_gain_staging_db(simple_bus_index));
+ ui->gainstaging_auto_checkbox->setChecked(global_audio_mixer->get_gain_staging_auto(simple_bus_index));
+ ui->compressor_enabled->setChecked(global_audio_mixer->get_compressor_enabled(simple_bus_index));
+ ui->compressor_threshold_db_display->setText(
+ QString::fromStdString(format_db(mixer->get_audio_mixer()->get_compressor_threshold_dbfs(simple_bus_index), DB_WITH_SIGN)));
+ }
connect(ui->locut_enabled, &QCheckBox::stateChanged, [this](int state){
- global_audio_mixer->set_locut_enabled(0, state == Qt::Checked);
+ global_audio_mixer->set_locut_enabled(simple_bus_index, state == Qt::Checked);
});
- ui->gainstaging_knob->setValue(global_audio_mixer->get_gain_staging_db());
- ui->gainstaging_auto_checkbox->setChecked(global_audio_mixer->get_gain_staging_auto());
- ui->compressor_enabled->setChecked(global_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_audio_mixer->set_gain_staging_auto(state == Qt::Checked);
+ connect(ui->gainstaging_knob, &QAbstractSlider::valueChanged,
+ bind(&MainWindow::gain_staging_knob_changed, this, simple_bus_index, _1));
+ connect(ui->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this, simple_bus_index](int state){
+ global_audio_mixer->set_gain_staging_auto(simple_bus_index, 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_audio_mixer->set_compressor_enabled(state == Qt::Checked);
+ connect(ui->compressor_threshold_knob, &QDial::valueChanged,
+ bind(&MainWindow::compressor_threshold_knob_changed, this, simple_bus_index, _1));
+ connect(ui->compressor_enabled, &QCheckBox::stateChanged, [this, simple_bus_index](int state){
+ global_audio_mixer->set_compressor_enabled(simple_bus_index, 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_audio_mixer->get_limiter_enabled());
- ui->makeup_gain_auto_checkbox->setChecked(global_audio_mixer->get_final_makeup_gain_auto());
+ // Global mastering controls.
QString limiter_threshold_label(
QString::fromStdString(format_db(mixer->get_audio_mixer()->get_limiter_threshold_dbfs(), DB_WITH_SIGN)));
ui->limiter_threshold_db_display->setText(limiter_threshold_label);
sigaction(SIGUSR1, &act, nullptr);
}
+void MainWindow::reset_audio_mapping_ui()
+{
+ bool simple = (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE);
+
+ ui->simple_audio_mode->setChecked(simple);
+ ui->multichannel_audio_mode->setChecked(!simple);
+ ui->input_mapping_action->setEnabled(!simple);
+
+ ui->locut_enabled->setVisible(simple);
+ ui->gainstaging_label->setVisible(simple);
+ ui->gainstaging_knob->setVisible(simple);
+ ui->gainstaging_db_display->setVisible(simple);
+ ui->gainstaging_auto_checkbox->setVisible(simple);
+ ui->compressor_threshold_label->setVisible(simple);
+ ui->compressor_threshold_knob->setVisible(simple);
+ ui->compressor_threshold_db_display->setVisible(simple);
+ ui->compressor_enabled->setVisible(simple);
+
+ setup_audio_miniview();
+ setup_audio_expanded_view();
+
+ if (simple) {
+ ui->audio_views->setCurrentIndex(0);
+ }
+ ui->compact_header->setVisible(!simple);
+}
+
void MainWindow::setup_audio_miniview()
{
// Remove any existing channels.
}
audio_miniviews.clear();
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) {
+ return;
+ }
+
// Set up brand new ones from the input mapping.
InputMapping mapping = global_audio_mixer->get_input_mapping();
audio_miniviews.resize(mapping.buses.size());
}
audio_expanded_views.clear();
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) {
+ return;
+ }
+
// Set up brand new ones from the input mapping.
InputMapping mapping = global_audio_mixer->get_input_mapping();
audio_expanded_views.resize(mapping.buses.size());
AboutDialog().exec();
}
+void MainWindow::simple_audio_mode_triggered()
+{
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) {
+ return;
+ }
+ unsigned card_index = global_audio_mixer->get_simple_input();
+ if (card_index == numeric_limits<unsigned>::max()) {
+ QMessageBox::StandardButton reply =
+ QMessageBox::question(this,
+ "Mapping too complex",
+ "The current audio mapping is too complicated to be representable in simple mode, "
+ "and will be discarded if you proceed. Really go to simple audio mode?",
+ QMessageBox::Yes | QMessageBox::No);
+ if (reply == QMessageBox::No) {
+ ui->simple_audio_mode->setChecked(false);
+ ui->multichannel_audio_mode->setChecked(true);
+ return;
+ }
+ card_index = 0;
+ }
+ global_audio_mixer->set_simple_input(/*card_index=*/card_index);
+ reset_audio_mapping_ui();
+}
+
+void MainWindow::multichannel_audio_mode_triggered()
+{
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL) {
+ return;
+ }
+
+ // Take the generated input mapping from the simple input,
+ // and set it as a normal multichannel mapping, which causes
+ // the mode to go to multichannel.
+ global_audio_mixer->set_input_mapping(global_audio_mixer->get_input_mapping());
+ reset_audio_mapping_ui();
+}
+
void MainWindow::input_mapping_triggered()
{
if (InputMappingDialog().exec() == QDialog::Accepted) {
remaining_height -= ui->vertical_layout->spacing();
// The label above the audio strip.
- double compact_label_height = ui->compact_label->geometry().height() +
+ double compact_label_height = ui->compact_label->minimumHeight() +
ui->compact_audio_layout->spacing();
remaining_height -= compact_label_height;
void x264_bitrate_triggered();
void exit_triggered();
void about_triggered();
+ void simple_audio_mode_triggered();
+ void multichannel_audio_mode_triggered();
void input_mapping_triggered();
void transition_clicked(int transition_number);
void channel_clicked(int channel_number);
void relayout();
private:
+ void reset_audio_mapping_ui();
void setup_audio_miniview();
void setup_audio_expanded_view();
bool eventFilter(QObject *watched, QEvent *event) override;
return theme->map_signal(channel);
}
- unsigned get_audio_source() const
- {
- return audio_source_channel;
- }
-
- void set_audio_source(unsigned channel)
- {
- audio_source_channel = channel;
- }
-
unsigned get_master_clock() const
{
return master_clock_channel;
<number>0</number>
</property>
<item>
- <layout class="QHBoxLayout" name="compact_header">
- <property name="spacing">
- <number>0</number>
+ <widget class="QWidget" name="compact_header" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>8</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <item>
- <widget class="QLabel" name="compact_label">
- <property name="text">
- <string>Compact audio view </string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="compact_prev_page">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="maximumSize">
- <size>
- <width>15</width>
- <height>15</height>
- </size>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- <property name="arrowType">
- <enum>Qt::LeftArrow</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="compact_next_page">
- <property name="maximumSize">
- <size>
- <width>15</width>
- <height>15</height>
- </size>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- <property name="arrowType">
- <enum>Qt::RightArrow</enum>
- </property>
- </widget>
- </item>
- </layout>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="compact_label">
+ <property name="text">
+ <string>Compact audio view </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="compact_prev_page">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>15</width>
+ <height>15</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::LeftArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="compact_next_page">
+ <property name="maximumSize">
+ <size>
+ <width>15</width>
+ <height>15</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::RightArrow</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
<layout class="QHBoxLayout" name="audiostrip" stretch="0,1,0">
<property name="title">
<string>&Audio</string>
</property>
+ <addaction name="simple_audio_mode"/>
+ <addaction name="multichannel_audio_mode"/>
+ <addaction name="separator"/>
<addaction name="input_mapping_action"/>
</widget>
<addaction name="menuWhat"/>
<string>&Input mapping…</string>
</property>
</action>
+ <action name="simple_audio_mode">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Simple</string>
+ </property>
+ </action>
+ <action name="multichannel_audio_mode">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Multichannel</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>