New Spectrum Analyzer (#4950)

Replace old spectrum analyzer by new one with higher resolution and
many new features.

Resolves #2847.
This commit is contained in:
Martin Pavelek
2019-07-17 22:45:26 +02:00
committed by Johannes Lorenz
parent 73c2c70d96
commit c3b4d5188a
42 changed files with 5329 additions and 751 deletions

View File

@@ -40,6 +40,8 @@ public:
EffectControlDialog( EffectControls * _controls );
virtual ~EffectControlDialog();
virtual bool isResizable() const {return false;}
signals:
void closed();

View File

@@ -30,7 +30,6 @@
#include <QGraphicsDropShadowEffect>
#include <QMdiSubWindow>
#include <QLabel>
#include <QPainter>
#include <QPushButton>
#include <QString>

View File

@@ -2,6 +2,7 @@
* fft_helpers.h - some functions around FFT analysis
*
* Copyright (c) 2008-2012 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2019 Martin Pavelek <he29.HS/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
@@ -28,57 +29,90 @@
#include "lmms_export.h"
#include <vector>
#include <fftw3.h>
const int FFT_BUFFER_SIZE = 2048;
// NOTE: FFT_BUFFER_SIZE should be considered deprecated!
// It is used by Eq plugin and some older code here, but this should be a user
// switchable parameter, not a constant. Use a value from FFT_BLOCK_SIZES
const unsigned int FFT_BUFFER_SIZE = 2048;
enum WINDOWS
// Allowed FFT block sizes. Ranging from barely useful to barely acceptable
// because of performance and latency reasons.
const std::vector<unsigned int> FFT_BLOCK_SIZES = {256, 512, 1024, 2048, 4096, 8192, 16384};
// List of FFT window functions supported by precomputeWindow()
enum FFT_WINDOWS
{
KAISER=1,
RECTANGLE,
HANNING,
HAMMING
RECTANGULAR = 0,
BLACKMAN_HARRIS,
HAMMING,
HANNING
};
/* returns biggest value from abs_spectrum[spec_size] array
/** Returns biggest value from abs_spectrum[spec_size] array.
*
* returns -1 on error
* @return -1 on error, 0 on success
*/
float LMMS_EXPORT maximum( float * _abs_spectrum, unsigned int _spec_size );
float LMMS_EXPORT maximum(const float *abs_spectrum, unsigned int spec_size);
float LMMS_EXPORT maximum(const std::vector<float> &abs_spectrum);
/* apply hanning or hamming window to channel
/** Normalize the abs_spectrum array of absolute values to a 0..1 range
* based on supplied energy and stores it in the norm_spectrum array.
*
* returns -1 on error
* @return -1 on error
*/
int LMMS_EXPORT hanming( float * _timebuffer, int _length, WINDOWS _type );
int LMMS_EXPORT normalize(const float *abs_spectrum, float *norm_spectrum, unsigned int bin_count, unsigned int block_size);
int LMMS_EXPORT normalize(const std::vector<float> &abs_spectrum, std::vector<float> &norm_spectrum, unsigned int block_size);
/* compute absolute values of complex_buffer, save to absspec_buffer
* take care that - compl_len is not bigger than complex_buffer!
* - absspec buffer is big enough!
/** Check if the spectrum contains any non-zero value.
*
* returns 0 on success, else -1
* @return 1 if spectrum contains any non-zero value
* @return 0 otherwise
*/
int LMMS_EXPORT absspec( fftwf_complex * _complex_buffer, float * _absspec_buffer,
int _compl_length );
int LMMS_EXPORT notEmpty(const std::vector<float> &spectrum);
/* build fewer subbands from many absolute spectrum values
* take care that - compressedbands[] array num_new elements long
* - num_old > num_new
/** Precompute a window function for later real-time use.
* Set normalized to false if you do not want to apply amplitude correction.
*
* returns 0 on success, else -1
* @return -1 on error
*/
int LMMS_EXPORT compressbands( float * _absspec_buffer, float * _compressedband,
int _num_old, int _num_new, int _bottom, int _top );
int LMMS_EXPORT precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool normalized = true);
int LMMS_EXPORT calc13octaveband31( float * _absspec_buffer, float * _subbands,
int _num_spec, float _max_frequency );
/* compute power of finite time sequence
* take care num_values is length of timesignal[]
/** Compute absolute values of complex_buffer, save to absspec_buffer.
* Take care that - compl_len is not bigger than complex_buffer!
* - absspec buffer is big enough!
*
* returns power on success, else -1
* @return 0 on success, else -1
*/
float LMMS_EXPORT signalpower(float *timesignal, int num_values);
int LMMS_EXPORT absspec(const fftwf_complex *complex_buffer, float *absspec_buffer,
unsigned int compl_length);
/** Build fewer subbands from many absolute spectrum values.
* Take care that - compressedbands[] array num_new elements long
* - num_old > num_new
*
* @return 0 on success, else -1
*/
int LMMS_EXPORT compressbands(const float * _absspec_buffer, float * _compressedband,
int _num_old, int _num_new, int _bottom, int _top);
int LMMS_EXPORT calc13octaveband31(float * _absspec_buffer, float * _subbands,
int _num_spec, float _max_frequency);
/** Compute power of finite time sequence.
* Take care num_values is length of timesignal[].
*
* @return power on success, else -1
*/
float LMMS_EXPORT signalpower(const float *timesignal, int num_values);
#endif

View File

@@ -0,0 +1,75 @@
/*
* Analyzer.cpp - definition of Analyzer class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* Based partially on Eq plugin code,
* Copyright (c) 2014-2017, David French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "Analyzer.h"
#include "embed.h"
#include "plugin_export.h"
extern "C" {
Plugin::Descriptor PLUGIN_EXPORT analyzer_plugin_descriptor =
{
"spectrumanalyzer",
"Spectrum Analyzer",
QT_TRANSLATE_NOOP("pluginBrowser", "A graphical spectrum analyzer."),
"Martin Pavelek <he29/dot/HS/at/gmail/dot/com>",
0x0100,
Plugin::Effect,
new PluginPixmapLoader("logo"),
NULL,
NULL
};
}
Analyzer::Analyzer(Model *parent, const Plugin::Descriptor::SubPluginFeatures::Key *key) :
Effect(&analyzer_plugin_descriptor, parent, key),
m_processor(&m_controls),
m_controls(this)
{
}
// Take audio data and pass them to the spectrum processor.
// Skip processing if the controls dialog isn't visible, it would only waste CPU cycles.
bool Analyzer::processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count)
{
if (!isEnabled() || !isRunning ()) {return false;}
if (m_controls.isViewVisible()) {m_processor.analyse(buffer, frame_count);}
return isRunning();
}
extern "C" {
// needed for getting plugin out of shared lib
PLUGIN_EXPORT Plugin *lmms_plugin_main(Model *parent, void *data)
{
return new Analyzer(parent, static_cast<const Plugin::Descriptor::SubPluginFeatures::Key *>(data));
}
}

View File

@@ -1,7 +1,9 @@
/*
* SpectrumAnalyzerControlDialog.h - view for spectrum analyzer
/* Analyzer.h - declaration of Analyzer class.
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* Based partially on Eq plugin code,
* Copyright (c) 2014-2017, David French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
@@ -22,32 +24,30 @@
*
*/
#ifndef _SPECTRUM_ANALYZER_CONTROL_DIALOG_H
#define _SPECTRUM_ANALYZER_CONTROL_DIALOG_H
#ifndef ANALYZER_H
#define ANALYZER_H
#include "EffectControlDialog.h"
#include "Effect.h"
#include "SaControls.h"
#include "SaProcessor.h"
class SpectrumAnalyzerControls;
class SpectrumAnalyzerControlDialog : public EffectControlDialog
//! Top level class; handles LMMS interface and feeds data to the data processor.
class Analyzer : public Effect
{
Q_OBJECT
public:
SpectrumAnalyzerControlDialog( SpectrumAnalyzerControls* controls );
virtual ~SpectrumAnalyzerControlDialog()
{
}
Analyzer(Model *parent, const Descriptor::SubPluginFeatures::Key *key);
virtual ~Analyzer() {};
bool processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) override;
EffectControls *controls() override {return &m_controls;}
SaProcessor *getProcessor() {return &m_processor;}
private:
virtual void paintEvent( QPaintEvent* event );
SaProcessor m_processor;
SaControls m_controls;
};
SpectrumAnalyzerControls* m_controls;
#endif // ANALYZER_H
QPixmap m_logXAxis;
QPixmap m_logYAxis;
} ;
#endif

View File

@@ -1,4 +1,5 @@
INCLUDE(BuildPlugin)
INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS})
LINK_LIBRARIES(${FFTW3F_LIBRARIES})
BUILD_PLUGIN(spectrumanalyzer SpectrumAnalyzer.cpp SpectrumAnalyzerControls.cpp SpectrumAnalyzerControlDialog.cpp SpectrumAnalyzer.h SpectrumAnalyzerControls.h SpectrumAnalyzerControlDialog.h MOCFILES SpectrumAnalyzerControlDialog.h SpectrumAnalyzerControls.h EMBEDDED_RESOURCES *.png)
BUILD_PLUGIN(analyzer Analyzer.cpp SaProcessor.cpp SaControls.cpp SaControlsDialog.cpp SaSpectrumView.cpp SaWaterfallView.cpp
MOCFILES SaProcessor.h SaControls.h SaControlsDialog.h SaSpectrumView.h SaWaterfallView.h EMBEDDED_RESOURCES *.svg logo.png)

View File

@@ -0,0 +1,19 @@
# Spectrum Analyzer plugin
## Overview
This plugin consists of three widgets and back-end code to provide them with required data.
The top-level widget is SaControlDialog. It populates a configuration widget (created dynamically) and instantiates spectrum display widgets. Its main back-end class is SaControls, which holds all configuration values and globally valid constants (e.g. range definitions).
SaSpectrumDisplay and SaWaterfallDisplay show the result of spectrum analysis. Their main back-end class is SaProcessor, which performs FFT analysis on data received from the Analyzer class, which in turn handles the interface with LMMS.
## Changelog
1.0.1 2019-06-02
- code style changes
- added tool-tips
- use const for unmodified arrays passed to fft_helpers
1.0.0 2019-04-07
- initial release

View File

@@ -0,0 +1,144 @@
/*
* SaControls.cpp - definition of SaControls class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SaControls.h"
#include <QtXml/QDomElement>
#include "Analyzer.h"
#include "SaControlsDialog.h"
SaControls::SaControls(Analyzer *effect) :
EffectControls(effect),
m_effect(effect),
// initialize bool models and set default values
m_pauseModel(false, this, tr("Pause")),
m_refFreezeModel(false, this, tr("Reference freeze")),
m_waterfallModel(false, this, tr("Waterfall")),
m_smoothModel(false, this, tr("Averaging")),
m_stereoModel(false, this, tr("Stereo")),
m_peakHoldModel(false, this, tr("Peak hold")),
m_logXModel(true, this, tr("Logarithmic frequency")),
m_logYModel(true, this, tr("Logarithmic amplitude")),
// default values of combo boxes are set after they are populated
m_freqRangeModel(this, tr("Frequency range")),
m_ampRangeModel(this, tr("Amplitude range")),
m_blockSizeModel(this, tr("FFT block size")),
m_windowModel(this, tr("FFT window type"))
{
// Frequency and amplitude ranges; order must match
// FREQUENCY_RANGES and AMPLITUDE_RANGES defined in SaControls.h
m_freqRangeModel.addItem(tr("Full (auto)"));
m_freqRangeModel.addItem(tr("Audible"));
m_freqRangeModel.addItem(tr("Bass"));
m_freqRangeModel.addItem(tr("Mids"));
m_freqRangeModel.addItem(tr("High"));
m_freqRangeModel.setValue(m_freqRangeModel.findText(tr("Full (auto)")));
m_ampRangeModel.addItem(tr("Extended"));
m_ampRangeModel.addItem(tr("Default"));
m_ampRangeModel.addItem(tr("Audible"));
m_ampRangeModel.addItem(tr("Noise"));
m_ampRangeModel.setValue(m_ampRangeModel.findText(tr("Default")));
// FFT block size labels are generated automatically, based on
// FFT_BLOCK_SIZES vector defined in fft_helpers.h
for (unsigned int i = 0; i < FFT_BLOCK_SIZES.size(); i++)
{
if (i == 0)
{
m_blockSizeModel.addItem((std::to_string(FFT_BLOCK_SIZES[i]) + " ").c_str() + tr("(High time res.)"));
}
else if (i == FFT_BLOCK_SIZES.size() - 1)
{
m_blockSizeModel.addItem((std::to_string(FFT_BLOCK_SIZES[i]) + " ").c_str() + tr("(High freq. res.)"));
}
else
{
m_blockSizeModel.addItem(std::to_string(FFT_BLOCK_SIZES[i]).c_str());
}
}
m_blockSizeModel.setValue(m_blockSizeModel.findText("2048"));
// Window type order must match FFT_WINDOWS defined in fft_helpers.h
m_windowModel.addItem(tr("Rectangular (Off)"));
m_windowModel.addItem(tr("Blackman-Harris (Default)"));
m_windowModel.addItem(tr("Hamming"));
m_windowModel.addItem(tr("Hanning"));
m_windowModel.setValue(m_windowModel.findText(tr("Blackman-Harris (Default)")));
// Colors
// Background color is defined by Qt / theme.
// Make sure the sum of colors for L and R channel stays lower or equal
// to 255. Otherwise the Waterfall pixels may overflow back to 0 even when
// the input signal isn't clipping (over 1.0).
m_colorL = QColor(51, 148, 204, 135);
m_colorR = QColor(204, 107, 51, 135);
m_colorMono = QColor(51, 148, 204, 204);
m_colorBG = QColor(7, 7, 7, 255); // ~20 % gray (after gamma correction)
m_colorGrid = QColor(30, 34, 38, 255); // ~40 % gray (slightly cold / blue)
m_colorLabels = QColor(192, 202, 212, 255); // ~90 % gray (slightly cold / blue)
}
// Create the SaControlDialog widget which handles display of GUI elements.
EffectControlDialog* SaControls::createView()
{
return new SaControlsDialog(this, m_effect->getProcessor());
}
void SaControls::loadSettings(const QDomElement &_this)
{
m_waterfallModel.loadSettings(_this, "Waterfall");
m_smoothModel.loadSettings(_this, "Smooth");
m_stereoModel.loadSettings(_this, "Stereo");
m_peakHoldModel.loadSettings(_this, "PeakHold");
m_logXModel.loadSettings(_this, "LogX");
m_logYModel.loadSettings(_this, "LogY");
m_freqRangeModel.loadSettings(_this, "RangeX");
m_ampRangeModel.loadSettings(_this, "RangeY");
m_blockSizeModel.loadSettings(_this, "BlockSize");
m_windowModel.loadSettings(_this, "WindowType");
}
void SaControls::saveSettings(QDomDocument &doc, QDomElement &parent)
{
m_waterfallModel.saveSettings(doc, parent, "Waterfall");
m_smoothModel.saveSettings(doc, parent, "Smooth");
m_stereoModel.saveSettings(doc, parent, "Stereo");
m_peakHoldModel.saveSettings(doc, parent, "PeakHold");
m_logXModel.saveSettings(doc, parent, "LogX");
m_logYModel.saveSettings(doc, parent, "LogY");
m_freqRangeModel.saveSettings(doc, parent, "RangeX");
m_ampRangeModel.saveSettings(doc, parent, "RangeY");
m_blockSizeModel.saveSettings(doc, parent, "BlockSize");
m_windowModel.saveSettings(doc, parent, "WindowType");
}

View File

@@ -0,0 +1,126 @@
/*
* SaControls.h - declaration of SaControls class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef SACONTROLS_H
#define SACONTROLS_H
#include "ComboBoxModel.h"
#include "EffectControls.h"
//#define SA_DEBUG 1 // define SA_DEBUG to enable performance measurements
// Frequency ranges (in Hz).
// Full range is defined by LOWEST_LOG_FREQ and current sample rate.
const int LOWEST_LOG_FREQ = 10; // arbitrary low limit for log. scale, >1
enum FREQUENCY_RANGES
{
FRANGE_FULL = 0,
FRANGE_AUDIBLE,
FRANGE_BASS,
FRANGE_MIDS,
FRANGE_HIGH
};
const int FRANGE_AUDIBLE_START = 20;
const int FRANGE_AUDIBLE_END = 20000;
const int FRANGE_BASS_START = 20;
const int FRANGE_BASS_END = 300;
const int FRANGE_MIDS_START = 200;
const int FRANGE_MIDS_END = 5000;
const int FRANGE_HIGH_START = 4000;
const int FRANGE_HIGH_END = 20000;
// Amplitude ranges.
// Reference: sine wave from -1.0 to 1.0 = 0 dB.
// I.e. if master volume is 100 %, positive values signify clipping.
// Doubling or halving the amplitude produces 3 dB difference.
enum AMPLITUDE_RANGES
{
ARANGE_EXTENDED = 0,
ARANGE_DEFAULT,
ARANGE_AUDIBLE,
ARANGE_NOISE
};
const int ARANGE_EXTENDED_START = -80;
const int ARANGE_EXTENDED_END = 20;
const int ARANGE_DEFAULT_START = -30;
const int ARANGE_DEFAULT_END = 0;
const int ARANGE_AUDIBLE_START = -50;
const int ARANGE_AUDIBLE_END = 10;
const int ARANGE_NOISE_START = -60;
const int ARANGE_NOISE_END = -20;
class Analyzer;
// Holds all the configuration values
class SaControls : public EffectControls
{
Q_OBJECT
public:
explicit SaControls(Analyzer* effect);
virtual ~SaControls() {}
EffectControlDialog* createView() override;
void saveSettings (QDomDocument& doc, QDomElement& parent) override;
void loadSettings (const QDomElement &_this) override;
QString nodeName() const override {return "Analyzer";}
int controlCount() override {return 12;}
private:
Analyzer *m_effect;
BoolModel m_pauseModel;
BoolModel m_refFreezeModel;
BoolModel m_waterfallModel;
BoolModel m_smoothModel;
BoolModel m_stereoModel;
BoolModel m_peakHoldModel;
BoolModel m_logXModel;
BoolModel m_logYModel;
ComboBoxModel m_freqRangeModel;
ComboBoxModel m_ampRangeModel;
ComboBoxModel m_blockSizeModel;
ComboBoxModel m_windowModel;
QColor m_colorL;
QColor m_colorR;
QColor m_colorMono;
QColor m_colorBG;
QColor m_colorGrid;
QColor m_colorLabels;
friend class SaControlsDialog;
friend class SaSpectrumView;
friend class SaWaterfallView;
friend class SaProcessor;
};
#endif // SACONTROLS_H

View File

@@ -0,0 +1,227 @@
/*
* SaControlsDialog.cpp - definition of SaControlsDialog class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SaControlsDialog.h"
#include <QGridLayout>
#include <QLabel>
#include <QSizePolicy>
#include <QSplitter>
#include <QWidget>
#include "ComboBox.h"
#include "ComboBoxModel.h"
#include "embed.h"
#include "Engine.h"
#include "LedCheckbox.h"
#include "PixmapButton.h"
#include "SaControls.h"
#include "SaProcessor.h"
// The entire GUI layout is built here.
SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) :
EffectControlDialog(controls),
m_controls(controls),
m_processor(processor)
{
// Top level placement of sections is handled by QSplitter widget.
QHBoxLayout *master_layout = new QHBoxLayout;
QSplitter *display_splitter = new QSplitter(Qt::Vertical);
master_layout->addWidget(display_splitter);
master_layout->setContentsMargins(2, 6, 2, 8);
setLayout(master_layout);
// QSplitter top: configuration section
QWidget *config_widget = new QWidget;
QGridLayout *config_layout = new QGridLayout;
config_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
config_widget->setMaximumHeight(m_configHeight);
config_widget->setLayout(config_layout);
display_splitter->addWidget(config_widget);
// Pre-compute target pixmap size based on monitor DPI.
// Using setDevicePixelRatio() on pixmap allows the SVG image to be razor
// sharp on High-DPI screens, but the desired size must be manually
// enlarged. No idea how to make Qt do it in a more reasonable way.
QSize iconSize = QSize(22.0 * devicePixelRatio(), 22.0 * devicePixelRatio());
QSize buttonSize = 1.2 * iconSize;
// pause and freeze buttons
PixmapButton *pauseButton = new PixmapButton(this, tr("Pause"));
pauseButton->setToolTip(tr("Pause data acquisition"));
QPixmap *pauseOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("play").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
QPixmap *pauseOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("pause").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
pauseOnPixmap->setDevicePixelRatio(devicePixelRatio());
pauseOffPixmap->setDevicePixelRatio(devicePixelRatio());
pauseButton->setActiveGraphic(*pauseOnPixmap);
pauseButton->setInactiveGraphic(*pauseOffPixmap);
pauseButton->setCheckable(true);
pauseButton->setModel(&controls->m_pauseModel);
config_layout->addWidget(pauseButton, 0, 0, 2, 1);
PixmapButton *refFreezeButton = new PixmapButton(this, tr("Reference freeze"));
refFreezeButton->setToolTip(tr("Freeze current input as a reference / disable falloff in peak-hold mode."));
QPixmap *freezeOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("freeze").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
QPixmap *freezeOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("freeze_off").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
freezeOnPixmap->setDevicePixelRatio(devicePixelRatio());
freezeOffPixmap->setDevicePixelRatio(devicePixelRatio());
refFreezeButton->setActiveGraphic(*freezeOnPixmap);
refFreezeButton->setInactiveGraphic(*freezeOffPixmap);
refFreezeButton->setCheckable(true);
refFreezeButton->setModel(&controls->m_refFreezeModel);
config_layout->addWidget(refFreezeButton, 2, 0, 2, 1);
// misc configuration switches
LedCheckBox *waterfallButton = new LedCheckBox(tr("Waterfall"), this);
waterfallButton->setToolTip(tr("Display real-time spectrogram"));
waterfallButton->setCheckable(true);
waterfallButton->setMinimumSize(70, 12);
waterfallButton->setModel(&controls->m_waterfallModel);
config_layout->addWidget(waterfallButton, 0, 1);
LedCheckBox *smoothButton = new LedCheckBox(tr("Averaging"), this);
smoothButton->setToolTip(tr("Enable exponential moving average"));
smoothButton->setCheckable(true);
smoothButton->setMinimumSize(70, 12);
smoothButton->setModel(&controls->m_smoothModel);
config_layout->addWidget(smoothButton, 1, 1);
LedCheckBox *stereoButton = new LedCheckBox(tr("Stereo"), this);
stereoButton->setToolTip(tr("Display stereo channels separately"));
stereoButton->setCheckable(true);
stereoButton->setMinimumSize(70, 12);
stereoButton->setModel(&controls->m_stereoModel);
config_layout->addWidget(stereoButton, 2, 1);
LedCheckBox *peakHoldButton = new LedCheckBox(tr("Peak hold"), this);
peakHoldButton->setToolTip(tr("Display envelope of peak values"));
peakHoldButton->setCheckable(true);
peakHoldButton->setMinimumSize(70, 12);
peakHoldButton->setModel(&controls->m_peakHoldModel);
config_layout->addWidget(peakHoldButton, 3, 1);
// frequency: linear / log. switch and range selector
PixmapButton *logXButton = new PixmapButton(this, tr("Logarithmic frequency"));
logXButton->setToolTip(tr("Switch between logarithmic and linear frequency scale"));
QPixmap *logXOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("x_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
QPixmap *logXOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("x_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
logXOnPixmap->setDevicePixelRatio(devicePixelRatio());
logXOffPixmap->setDevicePixelRatio(devicePixelRatio());
logXButton->setActiveGraphic(*logXOnPixmap);
logXButton->setInactiveGraphic(*logXOffPixmap);
logXButton->setCheckable(true);
logXButton->setModel(&controls->m_logXModel);
config_layout->addWidget(logXButton, 0, 2, 2, 1, Qt::AlignRight);
ComboBox *freqRangeCombo = new ComboBox(this, tr("Frequency range"));
freqRangeCombo->setToolTip(tr("Frequency range"));
freqRangeCombo->setMinimumSize(100, 22);
freqRangeCombo->setMaximumSize(200, 22);
freqRangeCombo->setModel(&controls->m_freqRangeModel);
config_layout->addWidget(freqRangeCombo, 0, 3, 2, 1);
// amplitude: linear / log switch and range selector
PixmapButton *logYButton = new PixmapButton(this, tr("Logarithmic amplitude"));
logYButton->setToolTip(tr("Switch between logarithmic and linear amplitude scale"));
QPixmap *logYOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("y_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
QPixmap *logYOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("y_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
logYOnPixmap->setDevicePixelRatio(devicePixelRatio());
logYOffPixmap->setDevicePixelRatio(devicePixelRatio());
logYButton->setActiveGraphic(*logYOnPixmap);
logYButton->setInactiveGraphic(*logYOffPixmap);
logYButton->setCheckable(true);
logYButton->setModel(&controls->m_logYModel);
config_layout->addWidget(logYButton, 2, 2, 2, 1, Qt::AlignRight);
ComboBox *ampRangeCombo = new ComboBox(this, tr("Amplitude range"));
ampRangeCombo->setToolTip(tr("Amplitude range"));
ampRangeCombo->setMinimumSize(100, 22);
ampRangeCombo->setMaximumSize(200, 22);
ampRangeCombo->setModel(&controls->m_ampRangeModel);
config_layout->addWidget(ampRangeCombo, 2, 3, 2, 1);
// FFT: block size: icon and selector
QLabel *blockSizeLabel = new QLabel("", this);
QPixmap *blockSizeIcon = new QPixmap(PLUGIN_NAME::getIconPixmap("block_size"));
blockSizeIcon->setDevicePixelRatio(devicePixelRatio());
blockSizeLabel->setPixmap(blockSizeIcon->scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
config_layout->addWidget(blockSizeLabel, 0, 4, 2, 1, Qt::AlignRight);
ComboBox *blockSizeCombo = new ComboBox(this, tr("FFT block bize"));
blockSizeCombo->setToolTip(tr("FFT block size"));
blockSizeCombo->setMinimumSize(100, 22);
blockSizeCombo->setMaximumSize(200, 22);
blockSizeCombo->setModel(&controls->m_blockSizeModel);
config_layout->addWidget(blockSizeCombo, 0, 5, 2, 1);
processor->reallocateBuffers();
connect(&controls->m_blockSizeModel, &ComboBoxModel::dataChanged, [=] {processor->reallocateBuffers();});
// FFT: window type: icon and selector
QLabel *windowLabel = new QLabel("", this);
QPixmap *windowIcon = new QPixmap(PLUGIN_NAME::getIconPixmap("window"));
windowIcon->setDevicePixelRatio(devicePixelRatio());
windowLabel->setPixmap(windowIcon->scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
config_layout->addWidget(windowLabel, 2, 4, 2, 1, Qt::AlignRight);
ComboBox *windowCombo = new ComboBox(this, tr("FFT window type"));
windowCombo->setToolTip(tr("FFT window type"));
windowCombo->setMinimumSize(100, 22);
windowCombo->setMaximumSize(200, 22);
windowCombo->setModel(&controls->m_windowModel);
config_layout->addWidget(windowCombo, 2, 5, 2, 1);
processor->rebuildWindow();
connect(&controls->m_windowModel, &ComboBoxModel::dataChanged, [=] {processor->rebuildWindow();});
// QSplitter middle and bottom: spectrum display widgets
m_spectrum = new SaSpectrumView(controls, processor, this);
display_splitter->addWidget(m_spectrum);
m_waterfall = new SaWaterfallView(controls, processor, this);
display_splitter->addWidget(m_waterfall);
m_waterfall->setVisible(m_controls->m_waterfallModel.value());
connect(&controls->m_waterfallModel, &BoolModel::dataChanged, [=] {m_waterfall->updateVisibility();});
}
// Suggest the best current widget size.
QSize SaControlsDialog::sizeHint() const
{
// Best width is determined by spectrum display sizeHint.
// Best height depends on whether waterfall is visible and
// consists of heights of the config section, spectrum, waterfall
// and some reserve for margins.
if (m_waterfall->isVisible())
{
return QSize(m_spectrum->sizeHint().width(),
m_configHeight + m_spectrum->sizeHint().height() + m_waterfall->sizeHint().height() + 50);
}
else
{
return QSize(m_spectrum->sizeHint().width(),
m_configHeight + m_spectrum->sizeHint().height() + 50);
}
}

View File

@@ -0,0 +1,57 @@
/*
* SaControlsDialog.h - declatation of SaControlsDialog class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef SACONTROLSDIALOG_H
#define SACONTROLSDIALOG_H
#include "EffectControlDialog.h"
#include "SaControls.h"
#include "SaSpectrumView.h"
#include "SaProcessor.h"
#include "SaWaterfallView.h"
//! Top-level widget holding the configuration GUI and spectrum displays
class SaControlsDialog : public EffectControlDialog
{
Q_OBJECT
public:
explicit SaControlsDialog(SaControls *controls, SaProcessor *processor);
virtual ~SaControlsDialog() {}
bool isResizable() const override {return true;}
QSize sizeHint() const override;
private:
SaControls *m_controls;
SaProcessor *m_processor;
// Pointers to created widgets are needed to keep track of their sizeHint() changes.
// Config widget is a plain QWidget so it has just a fixed height instead.
const int m_configHeight = 75;
SaSpectrumView *m_spectrum;
SaWaterfallView *m_waterfall;
};
#endif // SACONTROLSDIALOG_H

View File

@@ -0,0 +1,571 @@
/* SaProcessor.cpp - implementation of SaProcessor class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* Based partially on Eq plugin code,
* Copyright (c) 2014-2017, David French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SaProcessor.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <QMutexLocker>
#include "lmms_math.h"
SaProcessor::SaProcessor(SaControls *controls) :
m_controls(controls),
m_inBlockSize(FFT_BLOCK_SIZES[0]),
m_fftBlockSize(FFT_BLOCK_SIZES[0]),
m_sampleRate(Engine::mixer()->processingSampleRate()),
m_framesFilledUp(0),
m_spectrumActive(false),
m_waterfallActive(false),
m_waterfallNotEmpty(0),
m_reallocating(false)
{
m_fftWindow.resize(m_inBlockSize, 1.0);
precomputeWindow(m_fftWindow.data(), m_inBlockSize, BLACKMAN_HARRIS);
m_bufferL.resize(m_fftBlockSize, 0);
m_bufferR.resize(m_fftBlockSize, 0);
m_spectrumL = (fftwf_complex *) fftwf_malloc(binCount() * sizeof (fftwf_complex));
m_spectrumR = (fftwf_complex *) fftwf_malloc(binCount() * sizeof (fftwf_complex));
m_fftPlanL = fftwf_plan_dft_r2c_1d(m_fftBlockSize, m_bufferL.data(), m_spectrumL, FFTW_MEASURE);
m_fftPlanR = fftwf_plan_dft_r2c_1d(m_fftBlockSize, m_bufferR.data(), m_spectrumR, FFTW_MEASURE);
m_absSpectrumL.resize(binCount(), 0);
m_absSpectrumR.resize(binCount(), 0);
m_normSpectrumL.resize(binCount(), 0);
m_normSpectrumR.resize(binCount(), 0);
m_history.resize(binCount() * m_waterfallHeight * sizeof qRgb(0,0,0), 0);
clear();
}
SaProcessor::~SaProcessor()
{
if (m_fftPlanL != NULL) {fftwf_destroy_plan(m_fftPlanL);}
if (m_fftPlanR != NULL) {fftwf_destroy_plan(m_fftPlanR);}
if (m_spectrumL != NULL) {fftwf_free(m_spectrumL);}
if (m_spectrumR != NULL) {fftwf_free(m_spectrumR);}
m_fftPlanL = NULL;
m_fftPlanR = NULL;
m_spectrumL = NULL;
m_spectrumR = NULL;
}
// Load a batch of data from LMMS; run FFT analysis if buffer is full enough.
void SaProcessor::analyse(sampleFrame *in_buffer, const fpp_t frame_count)
{
#ifdef SA_DEBUG
int start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// only take in data if any view is visible and not paused
if ((m_spectrumActive || m_waterfallActive) && !m_controls->m_pauseModel.value())
{
const bool stereo = m_controls->m_stereoModel.value();
fpp_t in_frame = 0;
while (in_frame < frame_count)
{
// fill sample buffers and check for zero input
bool block_empty = true;
for (; in_frame < frame_count && m_framesFilledUp < m_inBlockSize; in_frame++, m_framesFilledUp++)
{
if (stereo)
{
m_bufferL[m_framesFilledUp] = in_buffer[in_frame][0];
m_bufferR[m_framesFilledUp] = in_buffer[in_frame][1];
}
else
{
m_bufferL[m_framesFilledUp] =
m_bufferR[m_framesFilledUp] = (in_buffer[in_frame][0] + in_buffer[in_frame][1]) * 0.5f;
}
if (in_buffer[in_frame][0] != 0.f || in_buffer[in_frame][1] != 0.f)
{
block_empty = false;
}
}
// Run analysis only if buffers contain enough data.
// Also, to prevent audio interruption and a momentary GUI freeze,
// skip analysis if buffers are being reallocated.
if (m_framesFilledUp < m_inBlockSize || m_reallocating) {return;}
// update sample rate
m_sampleRate = Engine::mixer()->processingSampleRate();
// apply FFT window
for (unsigned int i = 0; i < m_inBlockSize; i++)
{
m_bufferL[i] = m_bufferL[i] * m_fftWindow[i];
m_bufferR[i] = m_bufferR[i] * m_fftWindow[i];
}
// lock data shared with SaSpectrumView and SaWaterfallView
QMutexLocker lock(&m_dataAccess);
// Run FFT on left channel, convert the result to absolute magnitude
// spectrum and normalize it.
fftwf_execute(m_fftPlanL);
absspec(m_spectrumL, m_absSpectrumL.data(), binCount());
normalize(m_absSpectrumL, m_normSpectrumL, m_inBlockSize);
// repeat analysis for right channel if stereo processing is enabled
if (stereo)
{
fftwf_execute(m_fftPlanR);
absspec(m_spectrumR, m_absSpectrumR.data(), binCount());
normalize(m_absSpectrumR, m_normSpectrumR, m_inBlockSize);
}
// count empty lines so that empty history does not have to update
if (block_empty && m_waterfallNotEmpty)
{
m_waterfallNotEmpty -= 1;
}
else if (!block_empty)
{
m_waterfallNotEmpty = m_waterfallHeight + 2;
}
if (m_waterfallActive && m_waterfallNotEmpty)
{
// move waterfall history one line down and clear the top line
QRgb *pixel = (QRgb *)m_history.data();
std::copy(pixel,
pixel + binCount() * m_waterfallHeight - binCount(),
pixel + binCount());
memset(pixel, 0, binCount() * sizeof (QRgb));
// add newest result on top
int target; // pixel being constructed
float accL = 0; // accumulators for merging multiple bins
float accR = 0;
for (unsigned int i = 0; i < binCount(); i++)
{
// Every frequency bin spans a frequency range that must be
// partially or fully mapped to a pixel. Any inconsistency
// may be seen in the spectrogram as dark or white lines --
// play white noise to confirm your change did not break it.
float band_start = freqToXPixel(binToFreq(i) - binBandwidth() / 2.0, binCount());
float band_end = freqToXPixel(binToFreq(i + 1) - binBandwidth() / 2.0, binCount());
if (m_controls->m_logXModel.value())
{
// Logarithmic scale
if (band_end - band_start > 1.0)
{
// band spans multiple pixels: draw all pixels it covers
for (target = (int)band_start; target < (int)band_end; target++)
{
if (target >= 0 && target < binCount())
{
pixel[target] = makePixel(m_normSpectrumL[i], m_normSpectrumR[i]);
}
}
// save remaining portion of the band for the following band / pixel
// (in case the next band uses sub-pixel drawing)
accL = (band_end - (int)band_end) * m_normSpectrumL[i];
accR = (band_end - (int)band_end) * m_normSpectrumR[i];
}
else
{
// sub-pixel drawing; add contribution of current band
target = (int)band_start;
if ((int)band_start == (int)band_end)
{
// band ends within current target pixel, accumulate
accL += (band_end - band_start) * m_normSpectrumL[i];
accR += (band_end - band_start) * m_normSpectrumR[i];
}
else
{
// Band ends in the next pixel -- finalize the current pixel.
// Make sure contribution is split correctly on pixel boundary.
accL += ((int)band_end - band_start) * m_normSpectrumL[i];
accR += ((int)band_end - band_start) * m_normSpectrumR[i];
if (target >= 0 && target < binCount()) {pixel[target] = makePixel(accL, accR);}
// save remaining portion of the band for the following band / pixel
accL = (band_end - (int)band_end) * m_normSpectrumL[i];
accR = (band_end - (int)band_end) * m_normSpectrumR[i];
}
}
}
else
{
// Linear: always draws one or more pixels per band
for (target = (int)band_start; target < band_end; target++)
{
if (target >= 0 && target < binCount())
{
pixel[target] = makePixel(m_normSpectrumL[i], m_normSpectrumR[i]);
}
}
}
}
}
#ifdef SA_DEBUG
// report FFT processing speed
start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - start_time;
std::cout << "Processed " << m_framesFilledUp << " samples in " << start_time / 1000000.0 << " ms" << std::endl;
#endif
// clean up before checking for more data from input buffer
m_framesFilledUp = 0;
}
}
}
// Produce a spectrogram pixel from normalized spectrum data.
// Values over 1.0 will cause the color components to overflow: this is left
// intentionally untreated as it clearly indicates which frequency is clipping.
// Gamma correction is applied to make small values more visible and to make
// a linear gradient actually appear roughly linear. The correction should be
// around 0.42 to 0.45 for sRGB displays (or lower for bigger visibility boost).
QRgb SaProcessor::makePixel(float left, float right, float gamma_correction) const
{
if (m_controls->m_stereoModel.value())
{
float ampL = pow(left, gamma_correction);
float ampR = pow(right, gamma_correction);
return qRgb(m_controls->m_colorL.red() * ampL + m_controls->m_colorR.red() * ampR,
m_controls->m_colorL.green() * ampL + m_controls->m_colorR.green() * ampR,
m_controls->m_colorL.blue() * ampL + m_controls->m_colorR.blue() * ampR);
}
else
{
float ampL = pow(left, gamma_correction);
// make mono color brighter to compensate for the fact it is not summed
return qRgb(m_controls->m_colorMono.lighter().red() * ampL,
m_controls->m_colorMono.lighter().green() * ampL,
m_controls->m_colorMono.lighter().blue() * ampL);
}
}
// Inform the processor whether any display widgets actually need it.
void SaProcessor::setSpectrumActive(bool active)
{
m_spectrumActive = active;
}
void SaProcessor::setWaterfallActive(bool active)
{
m_waterfallActive = active;
}
// Reallocate data buffers according to newly set block size.
void SaProcessor::reallocateBuffers()
{
unsigned int new_size_index = m_controls->m_blockSizeModel.value();
unsigned int new_in_size, new_fft_size;
unsigned int new_bins;
// get new block sizes and bin count based on selected index
if (new_size_index < FFT_BLOCK_SIZES.size())
{
new_in_size = FFT_BLOCK_SIZES[new_size_index];
}
else
{
new_in_size = FFT_BLOCK_SIZES.back();
}
if (new_size_index + m_zeroPadFactor < FFT_BLOCK_SIZES.size())
{
new_fft_size = FFT_BLOCK_SIZES[new_size_index + m_zeroPadFactor];
}
else
{
new_fft_size = FFT_BLOCK_SIZES.back();
}
new_bins = new_fft_size / 2 +1;
// Lock data shared with SaSpectrumView and SaWaterfallView.
// The m_reallocating is here to tell analyse() to avoid asking for the
// lock, since fftw3 can take a while to find the fastest FFT algorithm
// for given machine, which would produce interruption in the audio stream.
m_reallocating = true;
QMutexLocker lock(&m_dataAccess);
// destroy old FFT plan and free the result buffer
if (m_fftPlanL != NULL) {fftwf_destroy_plan(m_fftPlanL);}
if (m_fftPlanR != NULL) {fftwf_destroy_plan(m_fftPlanR);}
if (m_spectrumL != NULL) {fftwf_free(m_spectrumL);}
if (m_spectrumR != NULL) {fftwf_free(m_spectrumR);}
// allocate new space, create new plan and resize containers
m_fftWindow.resize(new_in_size, 1.0);
precomputeWindow(m_fftWindow.data(), new_in_size, (FFT_WINDOWS) m_controls->m_windowModel.value());
m_bufferL.resize(new_fft_size, 0);
m_bufferR.resize(new_fft_size, 0);
m_spectrumL = (fftwf_complex *) fftwf_malloc(new_bins * sizeof (fftwf_complex));
m_spectrumR = (fftwf_complex *) fftwf_malloc(new_bins * sizeof (fftwf_complex));
m_fftPlanL = fftwf_plan_dft_r2c_1d(new_fft_size, m_bufferL.data(), m_spectrumL, FFTW_MEASURE);
m_fftPlanR = fftwf_plan_dft_r2c_1d(new_fft_size, m_bufferR.data(), m_spectrumR, FFTW_MEASURE);
if (m_fftPlanL == NULL || m_fftPlanR == NULL)
{
std::cerr << "Failed to create new FFT plan!" << std::endl;
}
m_absSpectrumL.resize(new_bins, 0);
m_absSpectrumR.resize(new_bins, 0);
m_normSpectrumL.resize(new_bins, 0);
m_normSpectrumR.resize(new_bins, 0);
m_history.resize(new_bins * m_waterfallHeight * sizeof qRgb(0,0,0), 0);
// done; publish new sizes and clean up
m_inBlockSize = new_in_size;
m_fftBlockSize = new_fft_size;
lock.unlock();
m_reallocating = false;
clear();
}
// Precompute a new FFT window based on currently selected type.
void SaProcessor::rebuildWindow()
{
// computation is done in fft_helpers
QMutexLocker lock(&m_dataAccess);
precomputeWindow(m_fftWindow.data(), m_inBlockSize, (FFT_WINDOWS) m_controls->m_windowModel.value());
}
// Clear all data buffers and replace contents with zeros.
// Note: may take a few milliseconds, do not call in a loop!
void SaProcessor::clear()
{
QMutexLocker lock(&m_dataAccess);
m_framesFilledUp = 0;
std::fill(m_bufferL.begin(), m_bufferL.end(), 0);
std::fill(m_bufferR.begin(), m_bufferR.end(), 0);
std::fill(m_absSpectrumL.begin(), m_absSpectrumL.end(), 0);
std::fill(m_absSpectrumR.begin(), m_absSpectrumR.end(), 0);
std::fill(m_normSpectrumL.begin(), m_normSpectrumL.end(), 0);
std::fill(m_normSpectrumR.begin(), m_normSpectrumR.end(), 0);
std::fill(m_history.begin(), m_history.end(), 0);
}
// --------------------------------------
// Frequency conversion helpers
//
// Get sample rate value that is valid for currently stored results.
unsigned int SaProcessor::getSampleRate() const
{
return m_sampleRate;
}
// Maximum frequency of a sampled signal is equal to half of its sample rate.
float SaProcessor::getNyquistFreq() const
{
return getSampleRate() / 2.0f;
}
// FFTW automatically discards upper half of the symmetric FFT output, so
// the useful bin count is the transform size divided by 2, plus zero.
unsigned int SaProcessor::binCount() const
{
return m_fftBlockSize / 2 + 1;
}
// Return the center frequency of given frequency bin.
float SaProcessor::binToFreq(unsigned int bin_index) const
{
return getNyquistFreq() * bin_index / binCount();
}
// Return width of the frequency range that falls into one bin.
// The binCount is lowered by one since half of the first and last bin is
// actually outside the frequency range.
float SaProcessor::binBandwidth() const
{
return getNyquistFreq() / (binCount() - 1);
}
float SaProcessor::getFreqRangeMin(bool linear) const
{
switch (m_controls->m_freqRangeModel.value())
{
case FRANGE_AUDIBLE: return FRANGE_AUDIBLE_START;
case FRANGE_BASS: return FRANGE_BASS_START;
case FRANGE_MIDS: return FRANGE_MIDS_START;
case FRANGE_HIGH: return FRANGE_HIGH_START;
default:
case FRANGE_FULL: return linear ? 0 : LOWEST_LOG_FREQ;
}
}
float SaProcessor::getFreqRangeMax() const
{
switch (m_controls->m_freqRangeModel.value())
{
case FRANGE_AUDIBLE: return FRANGE_AUDIBLE_END;
case FRANGE_BASS: return FRANGE_BASS_END;
case FRANGE_MIDS: return FRANGE_MIDS_END;
case FRANGE_HIGH: return FRANGE_HIGH_END;
default:
case FRANGE_FULL: return getNyquistFreq();
}
}
// Map frequency to pixel x position on a display of given width.
float SaProcessor::freqToXPixel(float freq, unsigned int width) const
{
if (m_controls->m_logXModel.value())
{
if (freq <= 1) {return 0;}
float min = log10(getFreqRangeMin());
float range = log10(getFreqRangeMax()) - min;
return (log10(freq) - min) / range * width;
}
else
{
float min = getFreqRangeMin();
float range = getFreqRangeMax() - min;
return (freq - min) / range * width;
}
}
// Map pixel x position on display of given width back to frequency.
float SaProcessor::xPixelToFreq(float x, unsigned int width) const
{
if (m_controls->m_logXModel.value())
{
float min = log10(getFreqRangeMin());
float max = log10(getFreqRangeMax());
float range = max - min;
return pow(10, min + x / width * range);
}
else
{
float min = getFreqRangeMin();
float range = getFreqRangeMax() - min;
return min + x / width * range;
}
}
// --------------------------------------
// Amplitude conversion helpers
//
float SaProcessor::getAmpRangeMin(bool linear) const
{
// return very low limit to make sure zero gets included at linear grid
if (linear) {return -900;}
switch (m_controls->m_ampRangeModel.value())
{
case ARANGE_EXTENDED: return ARANGE_EXTENDED_START;
case ARANGE_AUDIBLE: return ARANGE_AUDIBLE_START;
case ARANGE_NOISE: return ARANGE_NOISE_START;
default:
case ARANGE_DEFAULT: return ARANGE_DEFAULT_START;
}
}
float SaProcessor::getAmpRangeMax() const
{
switch (m_controls->m_ampRangeModel.value())
{
case ARANGE_EXTENDED: return ARANGE_EXTENDED_END;
case ARANGE_AUDIBLE: return ARANGE_AUDIBLE_END;
case ARANGE_NOISE: return ARANGE_NOISE_END;
default:
case ARANGE_DEFAULT: return ARANGE_DEFAULT_END;
}
}
// Map linear amplitude to pixel y position on a display of given height.
// Note that display coordinates are flipped: amplitude grows from [height] to zero.
float SaProcessor::ampToYPixel(float amplitude, unsigned int height) const
{
if (m_controls->m_logYModel.value())
{
// logarithmic scale: convert linear amplitude to dB (relative to 1.0)
float amplitude_dB = 10 * log10(amplitude);
if (amplitude_dB < getAmpRangeMin())
{
return height;
}
else
{
float max = getAmpRangeMax();
float range = getAmpRangeMin() - max;
return (amplitude_dB - max) / range * height;
}
}
else
{
// linear scale: convert returned ranges from dB to linear scale
float max = pow(10, getAmpRangeMax() / 10);
float range = pow(10, getAmpRangeMin() / 10) - max;
return (amplitude - max) / range * height;
}
}
// Map pixel y position on display of given height back to amplitude.
// Note that display coordinates are flipped: amplitude grows from [height] to zero.
// Also note that in logarithmic Y mode the returned amplitude is in dB, not linear.
float SaProcessor::yPixelToAmp(float y, unsigned int height) const
{
if (m_controls->m_logYModel.value())
{
float max = getAmpRangeMax();
float range = getAmpRangeMin() - max;
return max + range * (y / height);
}
else
{
// linear scale: convert returned ranges from dB to linear scale
float max = pow(10, getAmpRangeMax() / 10);
float range = pow(10, getAmpRangeMin() / 10) - max;
return max + range * (y / height);
}
}

View File

@@ -0,0 +1,122 @@
/* SaProcessor.h - declaration of SaProcessor class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* Based partially on Eq plugin code,
* Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef SAPROCESSOR_H
#define SAPROCESSOR_H
#include <QColor>
#include <QMutex>
#include <vector>
#include "fft_helpers.h"
#include "SaControls.h"
//! Receives audio data, runs FFT analysis and stores the result.
class SaProcessor
{
public:
explicit SaProcessor(SaControls *controls);
virtual ~SaProcessor();
void analyse(sampleFrame *in_buffer, const fpp_t frame_count);
// inform processor if any processing is actually required
void setSpectrumActive(bool active);
void setWaterfallActive(bool active);
// configuration is taken from models in SaControls; some changes require
// an exlicit update request (reallocation and window rebuild)
void reallocateBuffers();
void rebuildWindow();
void clear();
// information about results and unit conversion helpers
float binToFreq(unsigned int bin_index) const;
float binBandwidth() const;
float freqToXPixel(float frequency, unsigned int width) const;
float xPixelToFreq(float x, unsigned int width) const;
float ampToYPixel(float amplitude, unsigned int height) const;
float yPixelToAmp(float y, unsigned int height) const;
unsigned int getSampleRate() const;
float getNyquistFreq() const;
float getFreqRangeMin(bool linear = false) const;
float getFreqRangeMax() const;
float getAmpRangeMin(bool linear = false) const;
float getAmpRangeMax() const;
// data access lock must be acquired by any friendly class that touches
// the results, mainly to prevent unexpected mid-way reallocation
QMutex m_dataAccess;
private:
SaControls *m_controls;
// currently valid configuration
const unsigned int m_zeroPadFactor = 2; //!< use n-steps bigger FFT for given block size
unsigned int m_inBlockSize; //!< size of input (time domain) data block
unsigned int m_fftBlockSize; //!< size of padded block for FFT processing
unsigned int m_sampleRate;
unsigned int binCount() const; //!< size of output (frequency domain) data block
// data buffers (roughly in the order of processing, from input to output)
unsigned int m_framesFilledUp;
std::vector<float> m_bufferL; //!< time domain samples (left)
std::vector<float> m_bufferR; //!< time domain samples (right)
std::vector<float> m_fftWindow; //!< precomputed window function coefficients
fftwf_plan m_fftPlanL;
fftwf_plan m_fftPlanR;
fftwf_complex *m_spectrumL; //!< frequency domain samples (complex) (left)
fftwf_complex *m_spectrumR; //!< frequency domain samples (complex) (right)
std::vector<float> m_absSpectrumL; //!< frequency domain samples (absolute) (left)
std::vector<float> m_absSpectrumR; //!< frequency domain samples (absolute) (right)
std::vector<float> m_normSpectrumL; //!< frequency domain samples (normalized) (left)
std::vector<float> m_normSpectrumR; //!< frequency domain samples (normalized) (right)
// spectrum history for waterfall: new normSpectrum lines are added on top
std::vector<uchar> m_history;
const unsigned int m_waterfallHeight = 200; // Number of stored lines.
// Note: high values may make it harder to see transients.
// book keeping
bool m_spectrumActive;
bool m_waterfallActive;
unsigned int m_waterfallNotEmpty;
bool m_reallocating;
// merge L and R channels and apply gamma correction to make a spectrogram pixel
QRgb makePixel(float left, float right, float gamma_correction = 0.30) const;
friend class SaSpectrumView;
friend class SaWaterfallView;
};
#endif // SAPROCESSOR_H

View File

@@ -0,0 +1,796 @@
/* SaSpectrumView.cpp - implementation of SaSpectrumView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* Based partially on Eq plugin code,
* Copyright (c) 2014-2017, David French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SaSpectrumView.h"
#include <algorithm>
#include <cmath>
#include <QMouseEvent>
#include <QMutexLocker>
#include <QPainter>
#include <QString>
#include "GuiApplication.h"
#include "MainWindow.h"
#include "SaProcessor.h"
#ifdef SA_DEBUG
#include <chrono>
#include <iostream>
#endif
SaSpectrumView::SaSpectrumView(SaControls *controls, SaProcessor *processor, QWidget *_parent) :
QWidget(_parent),
m_controls(controls),
m_processor(processor),
m_freezeRequest(false),
m_frozen(false)
{
setMinimumSize(360, 170);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(gui->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(periodicUpdate()));
m_displayBufferL.resize(m_processor->binCount(), 0);
m_displayBufferR.resize(m_processor->binCount(), 0);
m_peakBufferL.resize(m_processor->binCount(), 0);
m_peakBufferR.resize(m_processor->binCount(), 0);
m_freqRangeIndex = m_controls->m_freqRangeModel.value();
m_ampRangeIndex = m_controls->m_ampRangeModel.value();
m_logFreqTics = makeLogFreqTics(m_processor->getFreqRangeMin(), m_processor->getFreqRangeMax());
m_linearFreqTics = makeLinearFreqTics(m_processor->getFreqRangeMin(), m_processor->getFreqRangeMax());
m_logAmpTics = makeLogAmpTics(m_processor->getAmpRangeMin(), m_processor->getAmpRangeMax());
m_linearAmpTics = makeLinearAmpTics(m_processor->getAmpRangeMin(), m_processor->getAmpRangeMax());
m_cursor = QPoint(0, 0);
}
// Compose and draw all the content; periodically called by Qt.
// NOTE: Performance sensitive! If the drawing takes too long, it will drag
// the FPS down for the entire program! Use SA_DEBUG to display timings.
void SaSpectrumView::paintEvent(QPaintEvent *event)
{
#ifdef SA_DEBUG
int total_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// 0) Constants and init
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// drawing and path-making are split into multiple methods for clarity;
// display boundaries are updated here and shared as member variables
m_displayTop = 1;
m_displayBottom = height() -20;
m_displayLeft = 26;
m_displayRight = width() -26;
m_displayWidth = m_displayRight - m_displayLeft;
// recompute range labels if needed
if (m_freqRangeIndex != m_controls->m_freqRangeModel.value())
{
m_logFreqTics = makeLogFreqTics(m_processor->getFreqRangeMin(), m_processor->getFreqRangeMax());
m_linearFreqTics = makeLinearFreqTics(m_processor->getFreqRangeMin(true), m_processor->getFreqRangeMax());
m_freqRangeIndex = m_controls->m_freqRangeModel.value();
}
if (m_ampRangeIndex != m_controls->m_ampRangeModel.value())
{
m_logAmpTics = makeLogAmpTics(m_processor->getAmpRangeMin(), m_processor->getAmpRangeMax());
m_linearAmpTics = makeLinearAmpTics(m_processor->getAmpRangeMin(true), m_processor->getAmpRangeMax());
m_ampRangeIndex = m_controls->m_ampRangeModel.value();
}
// generate freeze request or clear "frozen" status based on freeze button
if (!m_frozen && m_controls->m_refFreezeModel.value())
{
m_freezeRequest = true;
}
else if (!m_controls->m_refFreezeModel.value())
{
m_frozen = false;
}
// 1) Background, grid and labels
drawGrid(painter);
// 2) Spectrum display
drawSpectrum(painter);
// 3) Overlays
// draw cursor (if it is within bounds)
drawCursor(painter);
// always draw the display outline
painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawRoundedRect(m_displayLeft, 1,
m_displayWidth, m_displayBottom,
2.0, 2.0);
#ifdef SA_DEBUG
// display what FPS would be achieved if spectrum display ran in a loop
total_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - total_time;
painter.setPen(QPen(m_controls->m_colorLabels, 1,
Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawText(m_displayRight -100, 70, 100, 16, Qt::AlignLeft,
QString(std::string("Max FPS: " + std::to_string(1000000000.0 / total_time)).c_str()));
#endif
}
// Refresh data and draw the spectrum.
void SaSpectrumView::drawSpectrum(QPainter &painter)
{
#ifdef SA_DEBUG
int path_time = 0, draw_time = 0;
#endif
// draw the graph only if there is any input, averaging residue or peaks
QMutexLocker lock(&m_processor->m_dataAccess);
if (m_decaySum > 0 || notEmpty(m_processor->m_normSpectrumL) || notEmpty(m_processor->m_normSpectrumR))
{
lock.unlock();
#ifdef SA_DEBUG
path_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// update data buffers and reconstruct paths
refreshPaths();
#ifdef SA_DEBUG
path_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - path_time;
#endif
// draw stored paths
#ifdef SA_DEBUG
draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// in case stereo is disabled, mono data are stored in left channel structures
if (m_controls->m_stereoModel.value())
{
painter.fillPath(m_pathR, QBrush(m_controls->m_colorR));
painter.fillPath(m_pathL, QBrush(m_controls->m_colorL));
}
else
{
painter.fillPath(m_pathL, QBrush(m_controls->m_colorMono));
}
// draw the peakBuffer only if peak hold or reference freeze is active
if (m_controls->m_peakHoldModel.value() || m_controls->m_refFreezeModel.value())
{
if (m_controls->m_stereoModel.value())
{
painter.setPen(QPen(m_controls->m_colorR, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawPath(m_pathPeakR);
painter.setPen(QPen(m_controls->m_colorL, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawPath(m_pathPeakL);
}
else
{
painter.setPen(QPen(m_controls->m_colorL, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawPath(m_pathPeakL);
}
}
#ifdef SA_DEBUG
draw_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - draw_time;
#endif
}
else
{
lock.unlock();
}
#ifdef SA_DEBUG
// display measurement results
painter.drawText(m_displayRight -100, 90, 100, 16, Qt::AlignLeft,
QString(std::string("Path ms: " + std::to_string(path_time / 1000000.0)).c_str()));
painter.drawText(m_displayRight -100, 110, 100, 16, Qt::AlignLeft,
QString(std::string("Draw ms: " + std::to_string(draw_time / 1000000.0)).c_str()));
#endif
}
// Read newest FFT results from SaProcessor, update local display buffers
// and build QPainter paths.
void SaSpectrumView::refreshPaths()
{
// Lock is required for the entire function, mainly to prevent block size
// changes from causing reallocation of data structures mid-way.
QMutexLocker lock(&m_processor->m_dataAccess);
// check if bin count changed and reallocate display buffers accordingly
if (m_processor->binCount() != m_displayBufferL.size())
{
m_displayBufferL.clear();
m_displayBufferR.clear();
m_peakBufferL.clear();
m_peakBufferR.clear();
m_displayBufferL.resize(m_processor->binCount(), 0);
m_displayBufferR.resize(m_processor->binCount(), 0);
m_peakBufferL.resize(m_processor->binCount(), 0);
m_peakBufferR.resize(m_processor->binCount(), 0);
}
// update display buffers for left and right channel
#ifdef SA_DEBUG
int refresh_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
m_decaySum = 0;
updateBuffers(m_processor->m_normSpectrumL.data(), m_displayBufferL.data(), m_peakBufferL.data());
updateBuffers(m_processor->m_normSpectrumR.data(), m_displayBufferR.data(), m_peakBufferR.data());
#ifdef SA_DEBUG
refresh_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - refresh_time;
#endif
// if there was a freeze request, it was taken care of during the update
if (m_controls->m_refFreezeModel.value() && m_freezeRequest)
{
m_freezeRequest = false;
m_frozen = true;
}
#ifdef SA_DEBUG
int make_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// Use updated display buffers to prepare new paths for QPainter.
// This is the second slowest action (first is the subsequent drawing); use
// the resolution parameter to balance display quality and performance.
m_pathL = makePath(m_displayBufferL, 1.5);
if (m_controls->m_stereoModel.value())
{
m_pathR = makePath(m_displayBufferR, 1.5);
}
if (m_controls->m_peakHoldModel.value() || m_controls->m_refFreezeModel.value())
{
m_pathPeakL = makePath(m_peakBufferL, 0.25);
if (m_controls->m_stereoModel.value())
{
m_pathPeakR = makePath(m_peakBufferR, 0.25);
}
}
#ifdef SA_DEBUG
make_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - make_time;
#endif
#ifdef SA_DEBUG
// print measurement results
std::cout << "Buffer update ms: " << std::to_string(refresh_time / 1000000.0) << ", ";
std::cout << "Path-make ms: " << std::to_string(make_time / 1000000.0) << std::endl;
#endif
}
// Update display buffers: add new data, update average and peaks / reference.
// Output the sum of all displayed values -- draw only if it is non-zero.
// NOTE: The calling function is responsible for acquiring SaProcessor data
// access lock!
void SaSpectrumView::updateBuffers(float *spectrum, float *displayBuffer, float *peakBuffer)
{
for (int n = 0; n < m_processor->binCount(); n++)
{
// Update the exponential average if enabled, or simply copy the value.
if (!m_controls->m_pauseModel.value())
{
if (m_controls->m_smoothModel.value())
{
displayBuffer[n] = spectrum[n] * m_smoothFactor + displayBuffer[n] * (1 - m_smoothFactor);
}
else
{
displayBuffer[n] = spectrum[n];
}
}
// Update peak-hold and reference freeze data (using a shared curve).
// Peak hold and freeze can be combined: decay only if not frozen.
// Ref. freeze operates on the (possibly averaged) display buffer.
if (m_controls->m_refFreezeModel.value() && m_freezeRequest)
{
peakBuffer[n] = displayBuffer[n];
}
else if (m_controls->m_peakHoldModel.value() && !m_controls->m_pauseModel.value())
{
if (spectrum[n] > peakBuffer[n])
{
peakBuffer[n] = spectrum[n];
}
else if (!m_controls->m_refFreezeModel.value())
{
peakBuffer[n] = peakBuffer[n] * m_peakDecayFactor;
}
}
else if (!m_controls->m_refFreezeModel.value() && !m_controls->m_peakHoldModel.value())
{
peakBuffer[n] = 0;
}
// take note if there was actually anything to display
m_decaySum += displayBuffer[n] + peakBuffer[n];
}
}
// Use display buffer to build a path that can be drawn or filled by QPainter.
// Resolution controls the performance / quality tradeoff; the value specifies
// number of points in x axis per device pixel. Values over 1.0 still
// contribute to quality and accuracy thanks to anti-aliasing.
QPainterPath SaSpectrumView::makePath(std::vector<float> &displayBuffer, float resolution = 1.0)
{
// convert resolution to number of path points per logical pixel
float pixel_limit = resolution * window()->devicePixelRatio();
QPainterPath path;
path.moveTo(m_displayLeft, m_displayBottom);
// Translate frequency bins to path points.
// Display is flipped: y values grow towards zero, initial max is bottom.
// Bins falling to interval [x_start, x_next) contribute to a single point.
float max = m_displayBottom;
float x_start = -1; // lower bound of currently constructed point
for (unsigned int n = 0; n < m_processor->binCount(); n++)
{
float x = freqToXPixel(binToFreq(n), m_displayWidth);
float x_next = freqToXPixel(binToFreq(n + 1), m_displayWidth);
float y = ampToYPixel(displayBuffer[n], m_displayBottom);
// consider making a point only if x falls within display bounds
if (0 < x && x < m_displayWidth)
{
if (x_start == -1)
{
x_start = x;
// the first displayed bin is stretched to the left edge to prevent
// creating a misleading slope leading to zero (at log. scale)
path.lineTo(m_displayLeft, y + m_displayTop);
}
// Opt.: QPainter is very slow -- draw at most [pixel_limit] points
// per logical pixel. As opposed to limiting the bin count, this
// allows high resolution display if user resizes the analyzer.
// Look at bins that share the pixel and use the highest value:
max = y < max ? y : max;
// And make the final point in the middle of current interval.
if ((int)(x * pixel_limit) != (int)(x_next * pixel_limit))
{
x = (x + x_start) / 2;
path.lineTo(x + m_displayLeft, max + m_displayTop);
max = m_displayBottom;
x_start = x_next;
}
}
else
{
// stop processing after a bin falls outside right edge
// and align it to the edge to prevent a gap
if (n > 0 && x > 0)
{
path.lineTo(m_displayRight, y + m_displayTop);
break;
}
}
}
path.lineTo(m_displayRight, m_displayBottom);
path.closeSubpath();
return path;
}
// Draw background, grid and associated frequency and amplitude labels.
void SaSpectrumView::drawGrid(QPainter &painter)
{
std::vector<std::pair<int, std::string>> *freqTics = NULL;
std::vector<std::pair<float, std::string>> *ampTics = NULL;
float pos = 0;
float label_width = 24;
float label_height = 15;
float margin = 5;
// always draw the background
painter.fillRect(m_displayLeft, m_displayTop,
m_displayWidth, m_displayBottom,
m_controls->m_colorBG);
// select logarithmic or linear frequency grid and draw it
if (m_controls->m_logXModel.value())
{
freqTics = &m_logFreqTics;
}
else
{
freqTics = &m_linearFreqTics;
}
// draw frequency grid (line.first is display position)
painter.setPen(QPen(m_controls->m_colorGrid, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
for (auto &line: *freqTics)
{
painter.drawLine(m_displayLeft + freqToXPixel(line.first, m_displayWidth),
2,
m_displayLeft + freqToXPixel(line.first, m_displayWidth),
m_displayBottom);
}
// print frequency labels (line.second is label)
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
for (auto & line: *freqTics)
{
pos = m_displayLeft + freqToXPixel(line.first, m_displayWidth);
// align first and last label to the edge if needed, otherwise center them
if (line == freqTics->front() && pos - label_width / 2 < m_displayLeft)
{
painter.drawText(m_displayLeft, m_displayBottom + margin,
label_width, label_height, Qt::AlignLeft | Qt::TextDontClip,
QString(line.second.c_str()));
}
else if (line == freqTics->back() && pos + label_width / 2 > m_displayRight)
{
painter.drawText(m_displayRight - label_width, m_displayBottom + margin,
label_width, label_height, Qt::AlignRight | Qt::TextDontClip,
QString(line.second.c_str()));
}
else
{
painter.drawText(pos - label_width / 2, m_displayBottom + margin,
label_width, label_height, Qt::AlignHCenter | Qt::TextDontClip,
QString(line.second.c_str()));
}
}
margin = 2;
// select logarithmic or linear amplitude grid and draw it
if (m_controls->m_logYModel.value())
{
ampTics = &m_logAmpTics;
}
else
{
ampTics = &m_linearAmpTics;
}
// draw amplitude grid
painter.setPen(QPen(m_controls->m_colorGrid, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
for (auto & line: *ampTics)
{
painter.drawLine(m_displayLeft + 1,
ampToYPixel(line.first, m_displayBottom),
m_displayRight - 1,
ampToYPixel(line.first, m_displayBottom));
}
// print amplitude labels
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
bool stereo = m_controls->m_stereoModel.value();
for (auto & line: *ampTics)
{
pos = ampToYPixel(line.first, m_displayBottom);
// align first and last labels to edge if needed, otherwise center them
if (line == ampTics->back() && pos < 8)
{
if (stereo)
{
painter.setPen(QPen(m_controls->m_colorL.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
}
painter.drawText(m_displayLeft - label_width - margin, m_displayTop - 2,
label_width, label_height, Qt::AlignRight | Qt::AlignTop | Qt::TextDontClip,
QString(line.second.c_str()));
if (stereo)
{
painter.setPen(QPen(m_controls->m_colorR.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
}
painter.drawText(m_displayRight + margin, m_displayTop - 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip,
QString(line.second.c_str()));
}
else if (line == ampTics->front() && pos > m_displayBottom - label_height)
{
if (stereo)
{
painter.setPen(QPen(m_controls->m_colorL.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
}
painter.drawText(m_displayLeft - label_width - margin, m_displayBottom - label_height + 2,
label_width, label_height, Qt::AlignRight | Qt::AlignBottom | Qt::TextDontClip,
QString(line.second.c_str()));
if (stereo)
{
painter.setPen(QPen(m_controls->m_colorR.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
}
painter.drawText(m_displayRight + margin, m_displayBottom - label_height + 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip,
QString(line.second.c_str()));
}
else
{
if (stereo)
{
painter.setPen(QPen(m_controls->m_colorL.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
}
painter.drawText(m_displayLeft - label_width - margin, pos - label_height / 2,
label_width, label_height, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip,
QString(line.second.c_str()));
if (stereo)
{
painter.setPen(QPen(m_controls->m_colorR.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
}
painter.drawText(m_displayRight + margin, pos - label_height / 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip,
QString(line.second.c_str()));
}
}
}
// Draw cursor and its coordinates if it is within display bounds.
void SaSpectrumView::drawCursor(QPainter &painter)
{
if( m_cursor.x() >= m_displayLeft
&& m_cursor.x() <= m_displayRight
&& m_cursor.y() >= m_displayTop
&& m_cursor.y() <= m_displayBottom)
{
// cursor lines
painter.setPen(QPen(m_controls->m_colorGrid.lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawLine(m_cursor.x(), m_displayTop, m_cursor.x(), m_displayBottom);
painter.drawLine(m_displayLeft, m_cursor.y(), m_displayRight, m_cursor.y());
// coordinates
painter.setPen(QPen(m_controls->m_colorLabels.darker(), 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawText(m_displayRight -60, 5, 100, 16, Qt::AlignLeft, "Cursor");
QString tmps;
// frequency
int xFreq = (int)m_processor->xPixelToFreq(m_cursor.x() - m_displayLeft, m_displayWidth);
tmps = QString(std::string(std::to_string(xFreq) + " Hz").c_str());
painter.drawText(m_displayRight -60, 18, 100, 16, Qt::AlignLeft, tmps);
// amplitude
float yAmp = m_processor->yPixelToAmp(m_cursor.y(), m_displayBottom);
if (m_controls->m_logYModel.value())
{
tmps = QString(std::string(std::to_string(yAmp).substr(0, 5) + " dB").c_str());
}
else
{
// add 0.0005 to get proper rounding to 3 decimal places
tmps = QString(std::string(std::to_string(0.0005f + yAmp)).substr(0, 5).c_str());
}
painter.drawText(m_displayRight -60, 30, 100, 16, Qt::AlignLeft, tmps);
}
}
// Wrappers for most used SaProcessor helpers (to make local code more compact).
float SaSpectrumView::binToFreq(unsigned int bin_index)
{
return m_processor->binToFreq(bin_index);
}
float SaSpectrumView::freqToXPixel(float frequency, unsigned int width)
{
return m_processor->freqToXPixel(frequency, width);
}
float SaSpectrumView::ampToYPixel(float amplitude, unsigned int height)
{
return m_processor->ampToYPixel(amplitude, height);
}
// Generate labels suitable for logarithmic frequency scale.
// Low / high limits are in Hz. Lowest possible label is 10 Hz.
std::vector<std::pair<int, std::string>> SaSpectrumView::makeLogFreqTics(int low, int high)
{
std::vector<std::pair<int, std::string>> result;
int i, j;
int a[] = {10, 20, 50}; // sparse series multipliers
int b[] = {14, 30, 70}; // additional (denser) series
// generate main steps (powers of 10); use the series to specify smaller steps
for (i = 1; i <= high; i *= 10)
{
for (j = 0; j < 3; j++)
{
// insert a label from sparse series if it falls within bounds
if (i * a[j] >= low && i * a[j] <= high)
{
if (i * a[j] < 1000)
{
result.emplace_back(i * a[j], std::to_string(i * a[j]));
}
else
{
result.emplace_back(i * a[j], std::to_string(i * a[j] / 1000) + "k");
}
}
// also insert denser series if high and low values are close
if ((log10(high) - log10(low) < 2) && (i * b[j] >= low && i * b[j] <= high))
{
if (i * b[j] < 1500)
{
result.emplace_back(i * b[j], std::to_string(i * b[j]));
}
else
{
result.emplace_back(i * b[j], std::to_string(i * b[j] / 1000) + "k");
}
}
}
}
return result;
}
// Generate labels suitable for linear frequency scale.
// Low / high limits are in Hz.
std::vector<std::pair<int, std::string>> SaSpectrumView::makeLinearFreqTics(int low, int high)
{
std::vector<std::pair<int, std::string>> result;
int i, increment;
// select a suitable increment based on zoom level
if (high - low < 500) {increment = 50;}
else if (high - low < 1000) {increment = 100;}
else if (high - low < 5000) {increment = 1000;}
else {increment = 2000;}
// generate steps based on increment, starting at 0
for (i = 0; i <= high; i += increment)
{
if (i >= low)
{
if (i < 1000)
{
result.emplace_back(i, std::to_string(i));
}
else
{
result.emplace_back(i, std::to_string(i/1000) + "k");
}
}
}
return result;
}
// Generate labels suitable for logarithmic (dB) amplitude scale.
// Low / high limits are in dB; 0 dB amplitude = 1.0 linear.
// Treating results as power ratio, i.e., 3 dB should be about twice as loud.
std::vector<std::pair<float, std::string>> SaSpectrumView::makeLogAmpTics(int low, int high)
{
std::vector<std::pair<float, std::string>> result;
float i;
double increment;
// Base zoom level on selected range and how close is the current height
// to the sizeHint() (denser scale for bigger window).
if ((high - low) < 20 * ((float)height() / sizeHint().height()))
{
increment = pow(10, 0.3); // 3 dB steps when really zoomed in
}
else if (high - low < 45 * ((float)height() / sizeHint().height()))
{
increment = pow(10, 0.6); // 6 dB steps when sufficiently zoomed in
}
else
{
increment = 10; // 10 dB steps otherwise
}
// Generate n dB increments, start checking at -90 dB. Limits are tweaked
// just a little bit to make sure float comparisons do not miss edges.
for (i = 0.000000001; 10 * log10(i) <= (high + 0.001); i *= increment)
{
if (10 * log10(i) >= (low - 0.001))
{
result.emplace_back(i, std::to_string((int)std::round(10 * log10(i))));
}
}
return result;
}
// Generate labels suitable for linear amplitude scale.
// Low / high limits are in dB; 0 dB amplitude = 1.0 linear.
// Smallest possible label is 0.001, largest is 999. This includes the majority
// of useful labels; going lower or higher would require increasing margin size
// so that the text can fit. That would be a waste of space -- the linear scale
// would only make the experience worse for the main, logarithmic (dB) scale.
std::vector<std::pair<float, std::string>> SaSpectrumView::makeLinearAmpTics(int low, int high)
{
std::vector<std::pair<float, std::string>> result;
double i, nearest;
// make about 5 labels when window is small, 10 if it is big
float split = (float)height() / sizeHint().height() >= 1.5 ? 10.0 : 5.0;
// convert limits to linear scale
float lin_low = pow(10, low / 10.0);
float lin_high = pow(10, high / 10.0);
// Linear scale will vary widely, so instead of trying to craft extra nice
// multiples, just generate a few evenly spaced increments across the range,
// paying attention only to the decimal places to keep labels short.
// Limits are shifted a bit so that float comparisons do not miss edges.
for (i = 0; i <= (lin_high + 0.0001); i += (lin_high - lin_low) / split)
{
if (i >= (lin_low - 0.0001))
{
if (i >= 9.99 && i < 99.9)
{
nearest = std::round(i);
result.emplace_back(nearest, std::to_string(nearest).substr(0, 2));
}
else if (i >= 0.099)
{ // also covers numbers above 100
nearest = std::round(i * 10) / 10;
result.emplace_back(nearest, std::to_string(nearest).substr(0, 3));
}
else if (i >= 0.0099)
{
nearest = std::round(i * 1000) / 1000;
result.emplace_back(nearest, std::to_string(nearest).substr(0, 4));
}
else if (i >= 0.00099)
{
nearest = std::round(i * 10000) / 10000;
result.emplace_back(nearest, std::to_string(nearest).substr(1, 4));
}
else if (i > -0.01 && i < 0.01)
{
result.emplace_back(i, "0"); // an exception, zero is short..
}
}
}
return result;
}
// Periodic update is called by LMMS.
void SaSpectrumView::periodicUpdate()
{
// check if the widget is visible; if it is not, processing can be paused
m_processor->setSpectrumActive(isVisible());
// tell Qt it is time for repaint
update();
}
// Handle mouse input: set new cursor position.
void SaSpectrumView::mouseMoveEvent(QMouseEvent *event)
{
m_cursor = event->pos();
}
void SaSpectrumView::mousePressEvent(QMouseEvent *event)
{
m_cursor = event->pos();
}
// Handle resize event: rebuild grid and labels
void SaSpectrumView::resizeEvent(QResizeEvent *event)
{
// frequency does not change density with size
// amplitude does: rebuild labels
m_logAmpTics = makeLogAmpTics(m_processor->getAmpRangeMin(), m_processor->getAmpRangeMax());
m_linearAmpTics = makeLinearAmpTics(m_processor->getAmpRangeMin(), m_processor->getAmpRangeMax());
}

View File

@@ -0,0 +1,126 @@
/* SaSpectrumView.h - declaration of SaSpectrumView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* Based partially on Eq plugin code,
* Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef SASPECTRUMVIEW_H
#define SASPECTRUMVIEW_H
#include <string>
#include <utility>
#include <QPainterPath>
#include <QWidget>
class QMouseEvent;
class QPainter;
class SaControls;
class SaProcessor;
//! Widget that displays a spectrum curve and frequency / amplitude grid
class SaSpectrumView : public QWidget
{
Q_OBJECT
public:
explicit SaSpectrumView(SaControls *controls, SaProcessor *processor, QWidget *_parent = 0);
virtual ~SaSpectrumView() {}
QSize sizeHint() const override {return QSize(400, 200);}
protected:
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private slots:
void periodicUpdate();
private:
const SaControls *m_controls;
SaProcessor *m_processor;
// grid labels (position, label) and methods to generate them
std::vector<std::pair<int, std::string>> m_logFreqTics; // 10-20-50... Hz
std::vector<std::pair<int, std::string>> m_linearFreqTics; // 2k-4k-6k... Hz
std::vector<std::pair<float, std::string>> m_logAmpTics; // dB
std::vector<std::pair<float, std::string>> m_linearAmpTics; // 0..1
std::vector<std::pair<int, std::string>> makeLogFreqTics(int low, int high);
std::vector<std::pair<int, std::string>> makeLinearFreqTics(int low, int high);
std::vector<std::pair<float, std::string>> makeLogAmpTics(int low, int high);
std::vector<std::pair<float, std::string>> makeLinearAmpTics(int low, int high);
// currently selected ranges (see SaControls.h for enum definitions)
int m_freqRangeIndex;
int m_ampRangeIndex;
// draw the grid and all labels based on selected ranges
void drawGrid(QPainter &painter);
// local buffers for frequency bin values and a method to update them
// (mainly needed for averaging and to keep track of peak values)
std::vector<float> m_displayBufferL;
std::vector<float> m_displayBufferR;
std::vector<float> m_peakBufferL;
std::vector<float> m_peakBufferR;
void updateBuffers(float *spectrum, float *displayBuffer, float *peakBuffer);
// final paths to be drawn by QPainter and methods to build them
QPainterPath m_pathL;
QPainterPath m_pathR;
QPainterPath m_pathPeakL;
QPainterPath m_pathPeakR;
void refreshPaths();
QPainterPath makePath(std::vector<float> &displayBuffer, float resolution);
// helper variables for path drawing
float m_decaySum; // indicates if there is anything left to draw
bool m_freezeRequest; // new reference should be acquired
bool m_frozen; // a reference is currently stored in the peakBuffer
const float m_smoothFactor = 0.15; // alpha for exponential smoothing
const float m_peakDecayFactor = 0.992; // multiplier for gradual peak decay
// top level: refresh buffers, make paths and draw the spectrum
void drawSpectrum(QPainter &painter);
// current cursor location and a method to draw it
QPoint m_cursor;
void drawCursor(QPainter &painter);
// wrappers for most used SaProcessor conversion helpers
// (to make local code more readable)
float binToFreq(unsigned int bin_index);
float freqToXPixel(float frequency, unsigned int width);
float ampToYPixel(float amplitude, unsigned int height);
// current boundaries for drawing
unsigned int m_displayTop;
unsigned int m_displayBottom;
unsigned int m_displayLeft;
unsigned int m_displayRight;
unsigned int m_displayWidth;
};
#endif // SASPECTRUMVIEW_H

View File

@@ -0,0 +1,230 @@
/* SaWaterfallViewView.cpp - implementation of SaWaterfallViewView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SaWaterfallView.h"
#include <algorithm>
#include <cmath>
#include <QImage>
#include <QMutexLocker>
#include <QPainter>
#include <QSplitter>
#include <QString>
#include "EffectControlDialog.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "SaProcessor.h"
SaWaterfallView::SaWaterfallView(SaControls *controls, SaProcessor *processor, QWidget *_parent) :
QWidget(_parent),
m_controls(controls),
m_processor(processor)
{
m_controlDialog = (EffectControlDialog*) _parent;
setMinimumSize(300, 150);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(gui->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(periodicUpdate()));
m_timeTics = makeTimeTics();
m_oldTimePerLine = (float)m_processor->m_inBlockSize / m_processor->getSampleRate();
}
// Compose and draw all the content; called by Qt.
// Not as performance sensitive as SaSpectrumView, most of the processing is
// done directly in SaProcessor.
void SaWaterfallView::paintEvent(QPaintEvent *event)
{
#ifdef SA_DEBUG
int start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
#endif
// all drawing done here, local variables are sufficient for the boundary
const int displayTop = 1;
const int displayBottom = height() -2;
const int displayLeft = 26;
const int displayRight = width() -26;
const int displayWidth = displayRight - displayLeft;
float label_width = 20;
float label_height = 16;
float margin = 2;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// check if time labels need to be rebuilt
if ((float)m_processor->m_inBlockSize / m_processor->getSampleRate() != m_oldTimePerLine)
{
m_timeTics = makeTimeTics();
m_oldTimePerLine = (float)m_processor->m_inBlockSize / m_processor->getSampleRate();
}
// print time labels
float pos = 0;
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
for (auto & line: m_timeTics)
{
pos = timeToYPixel(line.first, displayBottom);
// align first and last label to the edge if needed, otherwise center them
if (line == m_timeTics.front() && pos < label_height / 2)
{
painter.drawText(displayLeft - label_width - margin, displayTop - 1,
label_width, label_height, Qt::AlignRight | Qt::AlignTop | Qt::TextDontClip,
QString(line.second.c_str()));
painter.drawText(displayRight + margin, displayTop - 1,
label_width, label_height, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip,
QString(line.second.c_str()));
}
else if (line == m_timeTics.back() && pos > displayBottom - label_height + 2)
{
painter.drawText(displayLeft - label_width - margin, displayBottom - label_height,
label_width, label_height, Qt::AlignRight | Qt::AlignBottom | Qt::TextDontClip,
QString(line.second.c_str()));
painter.drawText(displayRight + margin, displayBottom - label_height + 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip,
QString(line.second.c_str()));
}
else
{
painter.drawText(displayLeft - label_width - margin, pos - label_height / 2,
label_width, label_height, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip,
QString(line.second.c_str()));
painter.drawText(displayRight + margin, pos - label_height / 2,
label_width, label_height, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip,
QString(line.second.c_str()));
}
}
// draw the spectrogram precomputed in SaProcessor
if (m_processor->m_waterfallNotEmpty)
{
QMutexLocker lock(&m_processor->m_dataAccess);
painter.drawImage(displayLeft, displayTop, // top left corner coordinates
QImage(m_processor->m_history.data(), // raw pixel data to display
m_processor->binCount(), // width = number of frequency bins
m_processor->m_waterfallHeight, // height = number of history lines
QImage::Format_RGB32
).scaled(displayWidth, // scale to fit view..
displayBottom,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
lock.unlock();
}
else
{
painter.fillRect(displayLeft, displayTop, displayWidth, displayBottom, QColor(0,0,0));
}
// always draw the outline
painter.setPen(QPen(m_controls->m_colorGrid, 2, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawRoundedRect(displayLeft, displayTop, displayWidth, displayBottom, 2.0, 2.0);
#ifdef SA_DEBUG
// display what FPS would be achieved if waterfall ran in a loop
start_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - start_time;
painter.setPen(QPen(m_controls->m_colorLabels, 1, Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawText(displayRight -100, 10, 100, 16, Qt::AlignLeft,
QString(std::string("Max FPS: " + std::to_string(1000000000.0 / start_time)).c_str()));
#endif
}
// Convert time value to Y coordinate for display of given height.
float SaWaterfallView::timeToYPixel(float time, int height)
{
float pixels_per_line = (float)height / m_processor->m_waterfallHeight;
float seconds_per_line = ((float)m_processor->m_inBlockSize / m_processor->getSampleRate());
return pixels_per_line * time / seconds_per_line;
}
// Generate labels for linear time scale.
std::vector<std::pair<float, std::string>> SaWaterfallView::makeTimeTics()
{
std::vector<std::pair<float, std::string>> result;
float i;
// upper limit defined by number of lines * time per line
float limit = m_processor->m_waterfallHeight * ((float)m_processor->m_inBlockSize / m_processor->getSampleRate());
// set increment so that about 8 tics are generated
float increment = std::round(10 * limit / 7) / 10;
// NOTE: labels positions are rounded to match the (rounded) label value
for (i = 0; i <= limit; i += increment)
{
if (i < 10)
{
result.emplace_back(std::round(i * 10) / 10, std::to_string(std::round(i * 10) / 10).substr(0, 3));
}
else
{
result.emplace_back(std::round(i), std::to_string(std::round(i)).substr(0, 2));
}
}
return result;
}
// Periodically trigger repaint and check if the widget is visible.
// If it is not, stop drawing and inform the processor.
void SaWaterfallView::periodicUpdate()
{
m_processor->setWaterfallActive(isVisible());
if (isVisible()) {update();}
}
// Adjust window size and widget visibility when waterfall is enabled or disabbled.
void SaWaterfallView::updateVisibility()
{
// get container of the control dialog to be resized if needed
QWidget *subWindow = m_controlDialog->parentWidget();
if (m_controls->m_waterfallModel.value())
{
// clear old data before showing the waterfall
QMutexLocker lock(&m_processor->m_dataAccess);
std::fill(m_processor->m_history.begin(), m_processor->m_history.end(), 0);
lock.unlock();
setVisible(true);
// increase window size if it is too small
if (subWindow->size().height() < m_controlDialog->sizeHint().height())
{
subWindow->resize(subWindow->size().width(), m_controlDialog->sizeHint().height());
}
}
else
{
setVisible(false);
// decrease window size only if it does not violate sizeHint
subWindow->resize(subWindow->size().width(), m_controlDialog->sizeHint().height());
}
}

View File

@@ -0,0 +1,66 @@
/* SaWaterfallView.h - declaration of SaWaterfallView class.
*
* Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef SAWATERFALLVIEW_H
#define SAWATERFALLVIEW_H
#include <string>
#include <utility>
#include <vector>
#include <QPainter>
#include <QWidget>
#include "SaControls.h"
#include "SaProcessor.h"
// Widget that displays a spectrum waterfall (spectrogram) and time labels.
class SaWaterfallView : public QWidget
{
Q_OBJECT
public:
explicit SaWaterfallView(SaControls *controls, SaProcessor *processor, QWidget *_parent = 0);
virtual ~SaWaterfallView() {}
QSize sizeHint() const override {return QSize(400, 350);}
// Check if waterfall should be displayed and adjust window size if needed.
void updateVisibility();
protected:
void paintEvent(QPaintEvent *event) override;
private slots:
void periodicUpdate();
private:
const SaControls *m_controls;
SaProcessor *m_processor;
const EffectControlDialog *m_controlDialog;
// Methods and data used to make time labels
float m_oldTimePerLine;
float timeToYPixel(float time, int height);
std::vector<std::pair<float, std::string>> makeTimeTics();
std::vector<std::pair<float, std::string>> m_timeTics; // 0..n (s)
};
#endif // SAWATERFALLVIEW_H

View File

@@ -1,172 +0,0 @@
/*
* SpectrumAnalyzer.cpp - spectrum analyzer effect plugin
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SpectrumAnalyzer.h"
#include "embed.h"
#include "plugin_export.h"
extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT spectrumanalyzer_plugin_descriptor =
{
STRINGIFY( PLUGIN_NAME ),
"Spectrum Analyzer",
QT_TRANSLATE_NOOP( "pluginBrowser", "Graphical spectrum analyzer plugin" ),
"Tobias Doerffel <tobydox/at/users.sf.net>",
0x0100,
Plugin::Effect,
new PluginPixmapLoader(),
NULL,
NULL
} ;
}
SpectrumAnalyzer::SpectrumAnalyzer( Model * _parent,
const Descriptor::SubPluginFeatures::Key * _key ) :
Effect( &spectrumanalyzer_plugin_descriptor, _parent, _key ),
m_saControls( this ),
m_framesFilledUp( 0 ),
m_energy( 0 )
{
memset( m_buffer, 0, sizeof( m_buffer ) );
m_specBuf = (fftwf_complex *) fftwf_malloc( ( FFT_BUFFER_SIZE + 1 ) * sizeof( fftwf_complex ) );
m_fftPlan = fftwf_plan_dft_r2c_1d( FFT_BUFFER_SIZE*2, m_buffer, m_specBuf, FFTW_MEASURE );
}
SpectrumAnalyzer::~SpectrumAnalyzer()
{
fftwf_destroy_plan( m_fftPlan );
fftwf_free( m_specBuf );
}
bool SpectrumAnalyzer::processAudioBuffer( sampleFrame* _buf, const fpp_t _frames )
{
if( !isEnabled() || !isRunning () )
{
return false;
}
if( !m_saControls.isViewVisible() )
{
return true;
}
fpp_t f = 0;
if( _frames > FFT_BUFFER_SIZE )
{
m_framesFilledUp = 0;
f = _frames - FFT_BUFFER_SIZE;
}
const int cm = m_saControls.m_channelMode.value();
switch( cm )
{
case MergeChannels:
for( ; f < _frames; ++f )
{
m_buffer[m_framesFilledUp] =
( _buf[f][0] + _buf[f][1] ) * 0.5;
++m_framesFilledUp;
}
break;
case LeftChannel:
for( ; f < _frames; ++f )
{
m_buffer[m_framesFilledUp] = _buf[f][0];
++m_framesFilledUp;
}
break;
case RightChannel:
for( ; f < _frames; ++f )
{
m_buffer[m_framesFilledUp] = _buf[f][1];
++m_framesFilledUp;
}
break;
}
if( m_framesFilledUp < FFT_BUFFER_SIZE )
{
return isRunning();
}
// hanming( m_buffer, FFT_BUFFER_SIZE, HAMMING );
const sample_rate_t sr = Engine::mixer()->processingSampleRate();
const int LOWEST_FREQ = 0;
const int HIGHEST_FREQ = sr / 2;
fftwf_execute( m_fftPlan );
absspec( m_specBuf, m_absSpecBuf, FFT_BUFFER_SIZE+1 );
if( m_saControls.m_linearSpec.value() )
{
compressbands( m_absSpecBuf, m_bands, FFT_BUFFER_SIZE+1,
MAX_BANDS,
(int)(LOWEST_FREQ*(FFT_BUFFER_SIZE+1)/(float)(sr/2)),
(int)(HIGHEST_FREQ*(FFT_BUFFER_SIZE+1)/(float)(sr/2)));
m_energy = maximum( m_bands, MAX_BANDS ) / maximum( m_buffer, FFT_BUFFER_SIZE );
}
else
{
calc13octaveband31( m_absSpecBuf, m_bands, FFT_BUFFER_SIZE+1, sr/2.0 );
m_energy = signalpower( m_buffer, FFT_BUFFER_SIZE ) / maximum( m_buffer, FFT_BUFFER_SIZE );
}
m_framesFilledUp = 0;
checkGate( 1 );
return isRunning();
}
extern "C"
{
// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model* parent, void* data )
{
return new SpectrumAnalyzer( parent, static_cast<const Plugin::Descriptor::SubPluginFeatures::Key *>( data ) );
}
}

View File

@@ -1,78 +0,0 @@
/*
* SpectrumAnalyzer.h - spectrum anaylzer effect plugin
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef _SPECTRUM_ANALYZER_H
#define _SPECTRUM_ANALYZER_H
#include "Effect.h"
#include "fft_helpers.h"
#include "SpectrumAnalyzerControls.h"
const int MAX_BANDS = 249;
class SpectrumAnalyzer : public Effect
{
public:
enum ChannelModes
{
MergeChannels,
LeftChannel,
RightChannel
} ;
SpectrumAnalyzer( Model * _parent,
const Descriptor::SubPluginFeatures::Key * _key );
virtual ~SpectrumAnalyzer();
virtual bool processAudioBuffer( sampleFrame * _buf,
const fpp_t _frames );
virtual EffectControls * controls()
{
return( &m_saControls );
}
private:
SpectrumAnalyzerControls m_saControls;
fftwf_plan m_fftPlan;
fftwf_complex * m_specBuf;
float m_absSpecBuf[FFT_BUFFER_SIZE+1];
float m_buffer[FFT_BUFFER_SIZE*2];
int m_framesFilledUp;
float m_bands[MAX_BANDS];
float m_energy;
friend class SpectrumAnalyzerControls;
friend class SpectrumView;
} ;
#endif

View File

@@ -1,194 +0,0 @@
/*
* SpectrumAnalyzerControlDialog.cpp - view for spectrum analyzer
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <cmath>
#include <QLayout>
#include <QPainter>
#include "SpectrumAnalyzer.h"
#include "MainWindow.h"
#include "GuiApplication.h"
#include "LedCheckbox.h"
#include "embed.h"
static inline void darken( QImage& img, int x, int y, int w, int h )
{
int imgWidth = img.width();
QRgb * base = ( (QRgb *) img.bits() ) + y*imgWidth + x;
for( int y = 0; y < h; ++y )
{
QRgb * d = base + y*imgWidth;
for( int x = 0; x < w; ++x )
{
// shift each color component by 1 bit and set alpha
// to 0xff
d[x] = ( ( d[x] >> 1 ) & 0x7f7f7f7f ) | 0xff000000;
}
}
}
class SpectrumView : public QWidget
{
public:
SpectrumView( SpectrumAnalyzer* s, QWidget * _parent ) :
QWidget( _parent ),
m_sa( s ),
m_backgroundPlain( PLUGIN_NAME::getIconPixmap( "spectrum_background_plain" ).toImage() ),
m_background( PLUGIN_NAME::getIconPixmap( "spectrum_background" ).toImage() )
{
setFixedSize( 249, 151 );
connect( gui->mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) );
setAttribute( Qt::WA_OpaquePaintEvent, true );
}
virtual ~SpectrumView()
{
}
virtual void paintEvent( QPaintEvent* event )
{
QPainter p( this );
QImage i = m_sa->m_saControls.m_linearSpec.value() ?
m_backgroundPlain : m_background;
const float e = m_sa->m_energy;
if( e <= 0 )
{
darken( i, 0, 0, i.width(), i.height() );
p.drawImage( 0, 0, i );
return;
}
const bool lin_y = m_sa->m_saControls.m_linearYAxis.value();
float * b = m_sa->m_bands;
const int LOWER_Y = -60; // dB
int h;
const int fh = height();
if( m_sa->m_saControls.m_linearSpec.value() )
{
if( lin_y )
{
for( int x = 0; x < MAX_BANDS; ++x, ++b )
{
h = fh * 2.0 / 3.0 * (*b / e );
if( h < 0 ) h = 0; else if( h >= fh ) continue;
darken( i, x, 0, 1, fh-h );
}
}
else
{
for( int x = 0; x < MAX_BANDS; ++x, ++b )
{
h = (int)( fh * 2.0 / 3.0 * (20*(log10( *b / e ) ) - LOWER_Y ) / (-LOWER_Y ) );
if( h < 0 ) h = 0; else if( h >= fh ) continue;
darken( i, x, 0, 1, fh-h );
}
}
}
else
{
if( lin_y )
{
for( int x = 0; x < 31; ++x, ++b )
{
h = fh * 2.0 / 3.0 * ( 1.2 * *b / e );
if( h < 0 ) h = 0; else if( h >= fh ) continue; else h = ( h / 3 ) * 3;
darken( i, x*8, 0, 8, fh-h );
}
}
else
{
for( int x = 0; x < 31; ++x, ++b )
{
h = (int)( fh * 2.0 / 3.0 * (20*(log10( *b / e ) ) - LOWER_Y ) / (-LOWER_Y ) );
if( h < 0 ) h = 0; else if( h >= fh ) continue; else h = ( h / 3 ) * 3;
darken( i, x*8, 0, 8, fh-h );
}
}
darken( i, 31*8, 0, 1, fh );
}
p.drawImage( 0, 0, i );
}
private:
SpectrumAnalyzer * m_sa;
QImage m_backgroundPlain;
QImage m_background;
} ;
SpectrumAnalyzerControlDialog::SpectrumAnalyzerControlDialog( SpectrumAnalyzerControls* controls ) :
EffectControlDialog( controls ),
m_controls( controls ),
m_logXAxis( PLUGIN_NAME::getIconPixmap( "log_x_axis" ) ),
m_logYAxis( PLUGIN_NAME::getIconPixmap( "log_y_axis" ) )
{
setAutoFillBackground( true );
QPalette pal;
pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "background" ) );
setFixedSize( 293, 205 );
setPalette( pal );
/* QVBoxLayout * l = new QVBoxLayout( this );*/
SpectrumView* v = new SpectrumView( controls->m_effect, this );
v->move( 34, 10 );
LedCheckBox * lin_spec = new LedCheckBox( tr( "Linear spectrum" ), this );
lin_spec->move( 32, 182 );
lin_spec->setModel( &controls->m_linearSpec );
LedCheckBox * lin_y = new LedCheckBox( tr( "Linear Y axis" ), this );
lin_y->move( 137, 182 );
lin_y->setModel( &controls->m_linearYAxis );
connect( &controls->m_linearSpec, SIGNAL( dataChanged() ), this, SLOT( update() ) );
connect( &controls->m_linearYAxis, SIGNAL( dataChanged() ), this, SLOT( update() ) );
/* l->addWidget( v );
l->addWidget( lin_spec );
l->addWidget( lin_y );*/
}
void SpectrumAnalyzerControlDialog::paintEvent( QPaintEvent * )
{
QPainter p( this );
if( !m_controls->m_linearSpec.value() )
{
p.drawPixmap( 33, 165, m_logXAxis );
}
if( !m_controls->m_linearYAxis.value() )
{
p.drawPixmap( 10, 29, m_logYAxis);
}
}

View File

@@ -1,61 +0,0 @@
/*
* SpectrumAnalyzerControls.cpp - controls for spectrum analyzer
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SpectrumAnalyzer.h"
#include "SpectrumAnalyzerControls.h"
SpectrumAnalyzerControls::SpectrumAnalyzerControls( SpectrumAnalyzer* effect ) :
EffectControls( effect ),
m_effect( effect ),
m_linearSpec( false, this, tr( "Linear spectrum" ) ),
m_linearYAxis( false, this, tr( "Linear Y axis" ) ),
m_channelMode( SpectrumAnalyzer::MergeChannels,
SpectrumAnalyzer::MergeChannels,
SpectrumAnalyzer::RightChannel,
this, tr( "Channel mode" ) )
{
}
void SpectrumAnalyzerControls::loadSettings( const QDomElement & _this )
{
}
void SpectrumAnalyzerControls::saveSettings( QDomDocument & _doc,
QDomElement & _this )
{
}

View File

@@ -1,75 +0,0 @@
/*
* SpectrumAnalyzerControls.h - controls for spectrum-analyzer
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef SPECTRUM_ANALYZER_CONTROLS_H
#define SPECTRUM_ANALYZER_CONTROLS_H
#include "EffectControls.h"
#include "SpectrumAnalyzerControlDialog.h"
#include "Knob.h"
class SpectrumAnalyzer;
class SpectrumAnalyzerControls : public EffectControls
{
Q_OBJECT
public:
SpectrumAnalyzerControls( SpectrumAnalyzer* effect );
virtual ~SpectrumAnalyzerControls()
{
}
virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent );
virtual void loadSettings( const QDomElement & _this );
inline virtual QString nodeName() const
{
return "spectrumanaylzercontrols";
}
virtual int controlCount()
{
return 1;
}
virtual EffectControlDialog * createView()
{
return new SpectrumAnalyzerControlDialog( this );
}
private:
SpectrumAnalyzer* m_effect;
BoolModel m_linearSpec;
BoolModel m_linearYAxis;
IntModel m_channelMode;
friend class SpectrumAnalyzer;
friend class SpectrumAnalyzerControlDialog;
friend class SpectrumView;
} ;
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

View File

@@ -0,0 +1,273 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="block_size.svg.2019_03_31_01_06_33.0.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="TriangleOutM"
orient="auto"
refY="0"
refX="0"
id="TriangleOutM"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path8579"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.4)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInM"
orient="auto"
refY="0"
refX="0"
id="TriangleInM"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path8570"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.4)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="154.07316"
inkscape:cy="214.45272"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 72.999999,117 v 80"
id="path8424"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path8426"
d="m 148,117 v 80"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM);marker-end:url(#TriangleOutM)"
d="M 87.113439,142.15138 H 133.56041"
id="path8432"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path9420"
d="m 79.86528,184.07963 h 62.36081"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.09999943;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.10000002, 4.10000002;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g8730">
<path
id="path8736"
d="m 77.881439,142.15138 13.84,-8 v 16 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.60000008pt;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path8738"
d="m 142.79241,142.15138 -13.84,8 v -16 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.60000008pt;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -0,0 +1,296 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="freeze.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="61.391665"
inkscape:cy="165.33406"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="false"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-center="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 110,117 v 80"
id="path8424"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path8426"
d="M 75.358985,177 144.64101,137"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path8591"
d="M 144.64101,177 75.358984,137"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g8599">
<path
inkscape:transform-center-x="6.9453125"
inkscape:transform-center-y="-6.9453101"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 97.160779,123.8149 12.846151,12.84614"
id="path8593"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:connector-curvature="0"
id="path8595"
d="m 122.83921,123.8149 -12.84614,12.84614"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:transform-center-y="-6.9453101"
inkscape:transform-center-x="-6.94531"
sodipodi:nodetypes="cc" />
</g>
<use
x="0"
y="0"
xlink:href="#g8599"
id="use8515"
inkscape:transform-center-y="-17.121722"
width="100%"
height="100%"
transform="rotate(60,110,157)"
inkscape:transform-center-x="-27.10753" />
<use
x="0"
y="0"
xlink:href="#use8515"
inkscape:transform-center-x="-27.107531"
inkscape:transform-center-y="17.121718"
id="use8517"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use8517"
inkscape:transform-center-x="-4.7502381e-06"
inkscape:transform-center-y="27.2912"
id="use8519"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use8519"
inkscape:transform-center-x="27.107525"
inkscape:transform-center-y="17.121722"
id="use8521"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use8521"
inkscape:transform-center-x="27.107527"
inkscape:transform-center-y="-17.121718"
id="use8523"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,297 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="freeze_off.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="54.320595"
inkscape:cy="173.66782"
inkscape:document-units="mm"
inkscape:current-layer="g8599"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="false"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-center="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<path
style="fill:none;fill-rule:evenodd;stroke:#8a8e96;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 110,117 v 80"
id="path8424"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path8426"
d="M 75.358985,177 144.64101,137"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#8a8e96;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path8591"
d="M 144.64101,177 75.358984,137"
style="fill:none;fill-rule:evenodd;stroke:#8a8e96;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g8599"
style="stroke:#8a8e96;stroke-opacity:1">
<path
inkscape:transform-center-x="6.9453125"
inkscape:transform-center-y="-6.9453101"
style="fill:none;fill-rule:evenodd;stroke:#8a8e96;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 97.160779,123.8149 12.846151,12.84614"
id="path8593"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:connector-curvature="0"
id="path8595"
d="m 122.83921,123.8149 -12.84614,12.84614"
style="fill:none;fill-rule:evenodd;stroke:#8a8e96;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:transform-center-y="-6.9453101"
inkscape:transform-center-x="-6.94531"
sodipodi:nodetypes="cc" />
</g>
<use
x="0"
y="0"
xlink:href="#g8599"
id="use8515"
inkscape:transform-center-y="-16.857138"
width="100%"
height="100%"
transform="rotate(60,110,157)"
inkscape:transform-center-x="-26.746102" />
<use
x="0"
y="0"
xlink:href="#use8515"
inkscape:transform-center-x="-26.746104"
inkscape:transform-center-y="16.857132"
id="use8517"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use8517"
inkscape:transform-center-x="-8.3316985e-06"
inkscape:transform-center-y="27.026615"
id="use8519"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use8519"
inkscape:transform-center-x="26.746093"
inkscape:transform-center-y="16.857138"
id="use8521"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
<use
x="0"
y="0"
xlink:href="#use8521"
inkscape:transform-center-x="26.746095"
inkscape:transform-center-y="-16.857132"
id="use8523"
transform="rotate(60,110,157)"
width="100%"
height="100%" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

View File

@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="pause.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.5247505"
inkscape:cx="186.56659"
inkscape:cy="175.68099"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:18;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 88.874998,122 v 70"
id="path8424"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path8426"
d="m 132.12499,122 v 70"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:18;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="play.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="71.366921"
inkscape:cy="213.56884"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<g
id="g8730"
transform="translate(0,-10.583333)">
<path
id="path8738"
d="m 140.92066,167.57847 -63.353995,35.67994 v -71.3599 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.60000008pt;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 B

View File

@@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="99.73542mm"
height="99.73542mm"
viewBox="0 0 99.73542 99.73542"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="window.svg"
inkscape:export-filename="/home/martin/projects/software/lmms/plugins/SpectrumAnalyzer/window.svg.png"
inkscape:export-xdpi="16.299124"
inkscape:export-ydpi="16.299124">
<defs
id="defs2">
<marker
inkscape:stockid="TriangleOutM"
orient="auto"
refY="0"
refX="0"
id="TriangleOutM"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8579"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.4)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInM"
orient="auto"
refY="0"
refX="0"
id="TriangleInM"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8570"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.4)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="64.760601"
inkscape:cy="152.43979"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-60.13229,-107.13229)">
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 72.999999,117 v 80"
id="path8424"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path8426"
d="m 148,117 v 80"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 73.884273,181.83888 c 0,0 12.89641,-6.08542 18.543384,-20.6375 5.646974,-14.55209 7.824273,-34.925 17.909263,-34.925 10.08499,0 12.2623,20.37291 17.90927,34.925 5.64698,14.55208 18.54338,20.6375 18.54338,20.6375"
id="path8432"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cszsc" />
<path
inkscape:connector-curvature="0"
id="path9420"
d="M 79.675577,184.07963 H 142.5001"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.0999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.1, 4.1;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="x_linear.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="-138.52362"
inkscape:cy="185.47638"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<g
aria-label="X"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8591"
transform="translate(0,-1.0583333)">
<path
d="M 116.01015,174.93644 127.60979,192 h -8.97964 L 110.81495,180.57951 103.06693,192 H 94.0425 l 11.59964,-17.06356 -11.151777,-16.36937 h 9.002037 l 7.32255,10.77109 7.30015,-10.77109 h 9.04683 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:45.86111069px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path8505"
inkscape:connector-curvature="0" />
</g>
<g
transform="translate(-2.39e-4,-3.2244934)"
aria-label="LOG"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';letter-spacing:0px;word-spacing:0px;fill:#8a8e96;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8595-3">
<path
inkscape:connector-curvature="0"
d="m 77.419237,123.94899 h 5.977241 v 20.70502 h 10.47309 v 5.01262 H 77.419237 Z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#8a8e96;fill-opacity:1;stroke-width:0.26458332px"
id="path26803" />
<path
inkscape:connector-curvature="0"
d="m 107.01261,128.28981 q -2.72163,0 -4.23747,2.23931 -1.49862,2.23931 -1.49862,6.30452 0,4.04799 1.49862,6.2873 1.51584,2.23932 4.23747,2.23932 2.73885,0 4.23747,-2.23932 1.51584,-2.23931 1.51584,-6.2873 0,-4.06521 -1.51584,-6.30452 -1.49862,-2.23931 -4.23747,-2.23931 z m 0,-4.80591 q 5.58105,0 8.73332,3.54845 3.16948,3.54845 3.16948,9.80129 0,6.23563 -3.16948,9.78408 -3.15227,3.54844 -8.73332,3.54844 -5.56383,0 -8.733319,-3.54844 -3.169488,-3.54845 -3.169488,-9.78408 0,-6.25284 3.169488,-9.80129 3.169489,-3.54845 8.733319,-3.54845 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#8a8e96;fill-opacity:1;stroke-width:0.26458332px"
id="path26805" />
<path
inkscape:connector-curvature="0"
d="m 144.25409,147.7546 q -2.23931,1.20578 -4.65088,1.80867 -2.39434,0.60289 -4.94371,0.60289 -5.78776,0 -9.16395,-3.58289 -3.3762,-3.60013 -3.3762,-9.74963 0,-6.21839 3.42787,-9.78407 3.4451,-3.56567 9.43956,-3.56567 2.30822,0 4.42695,0.48231 2.11874,0.48232 3.99631,1.42972 v 5.32267 q -1.94648,-1.22301 -3.8585,-1.8259 -1.91203,-0.60289 -3.84128,-0.60289 -3.56568,0 -5.49493,2.22208 -1.92926,2.20487 -1.92926,6.32175 0,4.08244 1.86036,6.30453 1.86035,2.22209 5.28822,2.22209 0.93017,0 1.72255,-0.12058 0.80959,-0.13781 1.42971,-0.41341 v -4.99539 h -3.63458 v -4.44418 h 9.30176 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#8a8e96;fill-opacity:1;stroke-width:0.26458332px"
id="path26807" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="x_log.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="-140.02362"
inkscape:cy="188.97638"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<g
aria-label="LOG"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8595"
transform="translate(0,-3.2244934)">
<path
d="m 77.419237,123.94899 h 5.977241 v 20.70502 h 10.47309 v 5.01262 H 77.419237 Z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path26796"
inkscape:connector-curvature="0" />
<path
d="m 107.01261,128.28981 q -2.72163,0 -4.23747,2.23931 -1.49862,2.23931 -1.49862,6.30452 0,4.04799 1.49862,6.2873 1.51584,2.23932 4.23747,2.23932 2.73885,0 4.23747,-2.23932 1.51584,-2.23931 1.51584,-6.2873 0,-4.06521 -1.51584,-6.30452 -1.49862,-2.23931 -4.23747,-2.23931 z m 0,-4.80591 q 5.58105,0 8.73332,3.54845 3.16948,3.54845 3.16948,9.80129 0,6.23563 -3.16948,9.78408 -3.15227,3.54844 -8.73332,3.54844 -5.56383,0 -8.733319,-3.54844 -3.169488,-3.54845 -3.169488,-9.78408 0,-6.25284 3.169488,-9.80129 3.169489,-3.54845 8.733319,-3.54845 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path26798"
inkscape:connector-curvature="0" />
<path
d="m 144.25409,147.7546 q -2.23931,1.20578 -4.65088,1.80867 -2.39434,0.60289 -4.94371,0.60289 -5.78776,0 -9.16395,-3.58289 -3.3762,-3.60013 -3.3762,-9.74963 0,-6.21839 3.42787,-9.78407 3.4451,-3.56567 9.43956,-3.56567 2.30822,0 4.42695,0.48231 2.11874,0.48232 3.99631,1.42972 v 5.32267 q -1.94648,-1.22301 -3.8585,-1.8259 -1.91203,-0.60289 -3.84128,-0.60289 -3.56568,0 -5.49493,2.22208 -1.92926,2.20487 -1.92926,6.32175 0,4.08244 1.86036,6.30453 1.86035,2.22209 5.28822,2.22209 0.93017,0 1.72255,-0.12058 0.80959,-0.13781 1.42971,-0.41341 v -4.99539 h -3.63458 v -4.44418 h 9.30176 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path26800"
inkscape:connector-curvature="0" />
</g>
<g
aria-label="X"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8591"
transform="translate(0,-1.0583333)">
<path
d="M 116.01015,174.93644 127.60979,192 h -8.97964 L 110.81495,180.57951 103.06693,192 H 94.0425 l 11.59964,-17.06356 -11.151777,-16.36937 h 9.002037 l 7.32255,10.77109 7.30015,-10.77109 h 9.04683 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:45.86111069px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path35945"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="y_linear.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="-140.02362"
inkscape:cy="188.97638"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<g
aria-label="LOG"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';letter-spacing:0px;word-spacing:0px;fill:#8a8e96;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8595"
transform="translate(-2.39e-4,-3.2244934)">
<path
d="m 77.419237,123.94899 h 5.977241 v 20.70502 h 10.47309 v 5.01262 H 77.419237 Z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#8a8e96;fill-opacity:1;stroke-width:0.26458332px"
id="path26803"
inkscape:connector-curvature="0" />
<path
d="m 107.01261,128.28981 q -2.72163,0 -4.23747,2.23931 -1.49862,2.23931 -1.49862,6.30452 0,4.04799 1.49862,6.2873 1.51584,2.23932 4.23747,2.23932 2.73885,0 4.23747,-2.23932 1.51584,-2.23931 1.51584,-6.2873 0,-4.06521 -1.51584,-6.30452 -1.49862,-2.23931 -4.23747,-2.23931 z m 0,-4.80591 q 5.58105,0 8.73332,3.54845 3.16948,3.54845 3.16948,9.80129 0,6.23563 -3.16948,9.78408 -3.15227,3.54844 -8.73332,3.54844 -5.56383,0 -8.733319,-3.54844 -3.169488,-3.54845 -3.169488,-9.78408 0,-6.25284 3.169488,-9.80129 3.169489,-3.54845 8.733319,-3.54845 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#8a8e96;fill-opacity:1;stroke-width:0.26458332px"
id="path26805"
inkscape:connector-curvature="0" />
<path
d="m 144.25409,147.7546 q -2.23931,1.20578 -4.65088,1.80867 -2.39434,0.60289 -4.94371,0.60289 -5.78776,0 -9.16395,-3.58289 -3.3762,-3.60013 -3.3762,-9.74963 0,-6.21839 3.42787,-9.78407 3.4451,-3.56567 9.43956,-3.56567 2.30822,0 4.42695,0.48231 2.11874,0.48232 3.99631,1.42972 v 5.32267 q -1.94648,-1.22301 -3.8585,-1.8259 -1.91203,-0.60289 -3.84128,-0.60289 -3.56568,0 -5.49493,2.22208 -1.92926,2.20487 -1.92926,6.32175 0,4.08244 1.86036,6.30453 1.86035,2.22209 5.28822,2.22209 0.93017,0 1.72255,-0.12058 0.80959,-0.13781 1.42971,-0.41341 v -4.99539 h -3.63458 v -4.44418 h 9.30176 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#8a8e96;fill-opacity:1;stroke-width:0.26458332px"
id="path26807"
inkscape:connector-curvature="0" />
</g>
<g
aria-label="Y"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8591"
transform="translate(0,-1.0583333)">
<path
d="m 93.796176,158.56707 h 9.427504 l 7.61366,11.91314 7.61366,-11.91314 h 9.4499 l -12.74169,19.34766 V 192 h -8.62135 v -14.08527 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:45.86111069px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path35942"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="y_log.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8614"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
inkscape:connector-curvature="0"
transform="scale(0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8612" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker8538"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleInM">
<path
inkscape:connector-curvature="0"
transform="scale(-0.4)"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path8536" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="marker9923"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path9921"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8461"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8458"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9470"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9468"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker9424"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path9422"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8440"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.4,0,0,0.4,4,0)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8576"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0"
refX="0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8567"
d="M 5.77,0 -2.88,5 V -5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.00000003pt;stroke-opacity:1"
transform="scale(-0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="-140.02362"
inkscape:cy="188.97638"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-59.999998,-107)">
<g
aria-label="LOG"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8595"
transform="translate(0,-3.2244934)">
<path
d="m 77.419237,123.94899 h 5.977241 v 20.70502 h 10.47309 v 5.01262 H 77.419237 Z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332px"
id="path17641"
inkscape:connector-curvature="0" />
<path
d="m 107.01261,128.28981 q -2.72163,0 -4.23747,2.23931 -1.49862,2.23931 -1.49862,6.30452 0,4.04799 1.49862,6.2873 1.51584,2.23932 4.23747,2.23932 2.73885,0 4.23747,-2.23932 1.51584,-2.23931 1.51584,-6.2873 0,-4.06521 -1.51584,-6.30452 -1.49862,-2.23931 -4.23747,-2.23931 z m 0,-4.80591 q 5.58105,0 8.73332,3.54845 3.16948,3.54845 3.16948,9.80129 0,6.23563 -3.16948,9.78408 -3.15227,3.54844 -8.73332,3.54844 -5.56383,0 -8.733319,-3.54844 -3.169488,-3.54845 -3.169488,-9.78408 0,-6.25284 3.169488,-9.80129 3.169489,-3.54845 8.733319,-3.54845 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332px"
id="path17643"
inkscape:connector-curvature="0" />
<path
d="m 144.25409,147.7546 q -2.23931,1.20578 -4.65088,1.80867 -2.39434,0.60289 -4.94371,0.60289 -5.78776,0 -9.16395,-3.58289 -3.3762,-3.60013 -3.3762,-9.74963 0,-6.21839 3.42787,-9.78407 3.4451,-3.56567 9.43956,-3.56567 2.30822,0 4.42695,0.48231 2.11874,0.48232 3.99631,1.42972 v 5.32267 q -1.94648,-1.22301 -3.8585,-1.8259 -1.91203,-0.60289 -3.84128,-0.60289 -3.56568,0 -5.49493,2.22208 -1.92926,2.20487 -1.92926,6.32175 0,4.08244 1.86036,6.30453 1.86035,2.22209 5.28822,2.22209 0.93017,0 1.72255,-0.12058 0.80959,-0.13781 1.42971,-0.41341 v -4.99539 h -3.63458 v -4.44418 h 9.30176 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:35.27777863px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold Semi-Condensed';text-align:center;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332px"
id="path17645"
inkscape:connector-curvature="0" />
</g>
<g
aria-label="Y"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.76388884px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text8591"
transform="translate(0,-1.0583333)">
<path
d="m 93.796176,158.56707 h 9.427504 l 7.61366,11.91314 7.61366,-11.91314 h 9.4499 l -12.74169,19.34766 V 192 h -8.62135 v -14.08527 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:45.86111069px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.26458332px"
id="path17638"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -2,6 +2,7 @@
* fft_helpers.cpp - some functions around FFT analysis
*
* Copyright (c) 2008-2012 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2019 Martin Pavelek <he29.HS/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
@@ -28,127 +29,194 @@
#include <cmath>
#include "lmms_constants.h"
/* returns biggest value from abs_spectrum[spec_size] array
returns -1 on error
*/
float maximum(float *abs_spectrum, unsigned int spec_size)
/* Returns biggest value from abs_spectrum[spec_size] array.
*
* return -1 on error, otherwise the maximum value
*/
float maximum(const float *abs_spectrum, unsigned int spec_size)
{
float maxi=0;
float maxi = 0;
unsigned int i;
if ( abs_spectrum==NULL )
return -1;
if (abs_spectrum == NULL) {return -1;}
if (spec_size <= 0) {return -1;}
if (spec_size<=0)
return -1;
for ( i=0; i<spec_size; i++ )
for (i = 0; i < spec_size; i++)
{
if ( abs_spectrum[i]>maxi )
maxi=abs_spectrum[i];
if (abs_spectrum[i] > maxi) {maxi = abs_spectrum[i];}
}
return maxi;
}
float maximum(const std::vector<float> &abs_spectrum)
{
return maximum(abs_spectrum.data(), abs_spectrum.size());
}
/* apply hanning or hamming window to channel
returns -1 on error */
int hanming(float *timebuffer, int length, WINDOWS type)
/* Normalize the array of absolute magnitudes to a 0..1 range.
* Block size refers to FFT block size before any zero padding.
*
* return -1 on error, 0 on success
*/
int normalize(const float *abs_spectrum, float *norm_spectrum, unsigned int bin_count, unsigned int block_size)
{
int i;
float alpha;
if ( (timebuffer==NULL)||(length<=0) )
return -1;
if (abs_spectrum == NULL || norm_spectrum == NULL) {return -1;}
if (bin_count == 0 || block_size == 0) {return -1;}
for (i = 0; i < bin_count; i++)
{
norm_spectrum[i] = abs_spectrum[i] / block_size;
}
return 0;
}
int normalize(const std::vector<float> &abs_spectrum, std::vector<float> &norm_spectrum, unsigned int block_size)
{
if (abs_spectrum.size() != norm_spectrum.size()) {return -1;}
return normalize(abs_spectrum.data(), norm_spectrum.data(), abs_spectrum.size(), block_size);
}
/* Check if the spectrum contains any non-zero value.
*
* return 1 if spectrum contains any non-zero value
* return 0 otherwise
*/
int notEmpty(const std::vector<float> &spectrum)
{
for (int i = 0; i < spectrum.size(); i++)
{
if (spectrum[i] != 0) {return 1;}
}
return 0;
}
/* Precompute an FFT window function for later real-time use.
*
* return -1 on error
*/
int precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool normalized)
{
unsigned int i;
float gain = 0;
float a0;
float a1;
float a2;
float a3;
if (window == NULL) {return -1;}
// constants taken from
// https://en.wikipedia.org/wiki/Window_function#AList_of_window_functions
switch (type)
{
case HAMMING: alpha=0.54; break;
default:
case RECTANGULAR:
for (i = 0; i < length; i++) {window[i] = 1.0;}
gain = 1;
return 0;
case BLACKMAN_HARRIS:
a0 = 0.35875;
a1 = 0.48829;
a2 = 0.14128;
a3 = 0.01168;
break;
case HAMMING:
a0 = 0.54;
a1 = 1.0 - a0;
a2 = 0;
a3 = 0;
break;
case HANNING:
default: alpha=0.5; break;
a0 = 0.5;
a1 = 1.0 - a0;
a2 = 0;
a3 = 0;
break;
}
for ( i=0; i<length; i++ )
// common computation for cosine-sum based windows
for (i = 0; i < length; i++)
{
timebuffer[i]=timebuffer[i]*(alpha+(1-alpha)*cos(2*F_PI*i/((float)length-1.0)));
window[i] = (a0 - a1 * cos(2 * F_PI * i / ((float)length - 1.0))
+ a2 * cos(4 * F_PI * i / ((float)length - 1.0))
- a3 * cos(6 * F_PI * i / ((float)length - 1.0)));
gain += window[i];
}
// apply amplitude correction
gain /= (float) length;
for (i = 0; i < length; i++) {window[i] /= gain;}
return 0;
}
/* compute absolute values of complex_buffer, save to absspec_buffer
take care that - compl_len is not bigger than complex_buffer!
- absspec buffer is big enough!
returns 0 on success, else -1 */
int absspec(fftwf_complex *complex_buffer, float *absspec_buffer, int compl_length)
/* Compute absolute values of complex_buffer, save to absspec_buffer.
* Take care that - compl_len is not bigger than complex_buffer!
* - absspec buffer is big enough!
*
* return 0 on success, else -1
*/
int absspec(const fftwf_complex *complex_buffer, float *absspec_buffer, unsigned int compl_length)
{
int i;
if ( (complex_buffer==NULL)||(absspec_buffer==NULL) )
return -1;
if ( compl_length<=0 )
return -1;
if (complex_buffer == NULL || absspec_buffer == NULL) {return -1;}
if (compl_length <= 0) {return -1;}
for (i=0; i<compl_length; i++)
for (i = 0; i < compl_length; i++)
{
absspec_buffer[i]=(float )sqrt(complex_buffer[i][0]*complex_buffer[i][0] + complex_buffer[i][1]*complex_buffer[i][1]);
absspec_buffer[i] = (float)sqrt(complex_buffer[i][0] * complex_buffer[i][0]
+ complex_buffer[i][1] * complex_buffer[i][1]);
}
return 0;
}
/* build fewer subbands from many absolute spectrum values
take care that - compressedbands[] array num_new elements long
- num_old > num_new
returns 0 on success, else -1 */
int compressbands(float *absspec_buffer, float *compressedband, int num_old, int num_new, int bottom, int top)
/* Build fewer subbands from many absolute spectrum values.
* Take care that - compressedbands[] array num_new elements long
* - num_old > num_new
*
* return 0 on success, else -1
*/
int compressbands(const float *absspec_buffer, float *compressedband, int num_old, int num_new, int bottom, int top)
{
float ratio;
int i, usefromold;
float j;
float j_min, j_max;
if ( (absspec_buffer==NULL)||(compressedband==NULL) )
return -1;
if (absspec_buffer == NULL || compressedband == NULL) {return -1;}
if (num_old < num_new) {return -1;}
if (num_old <= 0 || num_new <= 0) {return -1;}
if (bottom < 0) {bottom = 0;}
if (top >= num_old) {top = num_old - 1;}
if ( num_old<num_new )
return -1;
usefromold = num_old - (num_old - top) - bottom;
if ( (num_old<=0)||(num_new<=0) )
return -1;
if ( bottom<0 )
bottom=0;
if ( top>=num_old )
top=num_old-1;
usefromold=num_old-(num_old-top)-bottom;
ratio=(float)usefromold/(float)num_new;
ratio = (float)usefromold / (float)num_new;
// for each new subband
for ( i=0; i<num_new; i++ )
for (i = 0; i < num_new; i++)
{
compressedband[i]=0;
compressedband[i] = 0;
j_min=(i*ratio)+bottom;
j_min = (i * ratio) + bottom;
if ( j_min<0 )
j_min=bottom;
if (j_min < 0) {j_min = bottom;}
j_max=j_min+ratio;
j_max = j_min + ratio;
for ( j=(int)j_min; j<=j_max; j++ )
for (j = (int)j_min; j <= j_max; j++)
{
compressedband[i]+=absspec_buffer[(int)j];
compressedband[i] += absspec_buffer[(int)j];
}
}
@@ -158,84 +226,73 @@ int compressbands(float *absspec_buffer, float *compressedband, int num_old, int
int calc13octaveband31(float *absspec_buffer, float *subbands, int num_spec, float max_frequency)
{
static const int onethirdoctavecenterfr[] = {20, 25, 31, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000};
static const int onethirdoctavecenterfr[] = {20, 25, 31, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000};
int i, j;
float f_min, f_max, frequency, bandwidth;
int j_min, j_max=0;
int j_min, j_max = 0;
float fpower;
if ( (absspec_buffer==NULL)||(subbands==NULL) )
return -1;
if ( num_spec<31 )
return -1;
if ( max_frequency<=0 )
return -1;
if (absspec_buffer == NULL || subbands == NULL) {return -1;}
if (num_spec < 31) {return -1;}
if (max_frequency <= 0) {return -1;}
/*** energy ***/
fpower=0;
for ( i=0; i<num_spec; i++ )
fpower = 0;
for (i = 0; i < num_spec; i++)
{
absspec_buffer[i]=(absspec_buffer[i]*absspec_buffer[i])/FFT_BUFFER_SIZE;
fpower=fpower+(2*absspec_buffer[i]);
absspec_buffer[i] = (absspec_buffer[i] * absspec_buffer[i]) / FFT_BUFFER_SIZE;
fpower = fpower + (2 * absspec_buffer[i]);
}
fpower=fpower-(absspec_buffer[0]); //dc not mirrored
fpower = fpower - (absspec_buffer[0]); //dc not mirrored
/*** for each subband: sum up power ***/
for ( i=0; i<31; i++ )
for (i = 0; i < 31; i++)
{
subbands[i]=0;
subbands[i] = 0;
// calculate bandwidth for subband
frequency=onethirdoctavecenterfr[i];
frequency = onethirdoctavecenterfr[i];
bandwidth=(pow(2, 1.0/3.0)-1)*frequency;
bandwidth = (pow(2, 1.0/3.0)-1) * frequency;
f_min=frequency-bandwidth/2.0;
f_max=frequency+bandwidth/2.0;
f_min = frequency - bandwidth / 2.0;
f_max = frequency + bandwidth / 2.0;
j_min=(int)(f_min/max_frequency*(float)num_spec);
j_max=(int)(f_max/max_frequency*(float)num_spec);
j_min = (int)(f_min / max_frequency * (float)num_spec);
j_max = (int)(f_max / max_frequency * (float)num_spec);
if ( (j_min<0)||(j_max<0) )
if (j_min < 0 || j_max < 0)
{
fprintf(stderr, "Error: calc13octaveband31() in fft_helpers.cpp line %d failed.\n", __LINE__);
return -1;
}
for ( j=j_min; j<=j_max; j++ )
for (j = j_min; j <= j_max; j++)
{
if( j_max<num_spec )
subbands[i]+=absspec_buffer[j];
if (j_max < num_spec) {subbands[i] += absspec_buffer[j];}
}
} //for
return 0;
}
/* compute power of finite time sequence
take care num_values is length of timesignal[]
returns power on success, else -1 */
float signalpower(float *timesignal, int num_values)
/* Compute power of finite time sequence.
* Take care num_values is length of timesignal[]
*
* return power on success, else -1
*/
float signalpower(const float *timesignal, int num_values)
{
if ( num_values<=0 )
return -1;
if (num_values <= 0) {return -1;}
if( timesignal==NULL )
return -1;
if (timesignal == NULL) {return -1;}
float power=0;
for ( int i=0; i<num_values; i++ )
float power = 0;
for (int i = 0; i < num_values; i++)
{
power+=timesignal[i]*timesignal[i];
power += timesignal[i] * timesignal[i];
}
return power;

View File

@@ -625,6 +625,7 @@ SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags
SubWindow *win = new SubWindow(m_workspace->viewport(), windowFlags);
win->setAttribute(Qt::WA_DeleteOnClose);
win->setWidget(w);
if (w->sizeHint().isValid()) {win->resize(w->sizeHint());}
m_workspace->addSubWindow(win);
return win;
}

View File

@@ -30,6 +30,7 @@
#include <QMdiArea>
#include <QMoveEvent>
#include <QPainter>
#include <QScrollBar>
#include "embed.h"

View File

@@ -97,9 +97,14 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) :
if( m_controlView )
{
m_subWindow = gui->mainWindow()->addWindowedWidget( m_controlView );
m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
if (m_subWindow->layout()) {
m_subWindow->layout()->setSizeConstraint(QLayout::SetFixedSize);
if ( !m_controlView->isResizable() )
{
m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
if (m_subWindow->layout())
{
m_subWindow->layout()->setSizeConstraint(QLayout::SetFixedSize);
}
}
Qt::WindowFlags flags = m_subWindow->windowFlags();

View File

@@ -134,11 +134,11 @@ QSize PixmapButton::sizeHint() const
{
if( ( model() != NULL && model()->value() ) || m_pressed )
{
return m_activePixmap.size();
return m_activePixmap.size() / devicePixelRatio();
}
else
{
return m_inactivePixmap.size();
return m_inactivePixmap.size() / devicePixelRatio();
}
}