Skip to content

Commit

Permalink
New Spectrum Analyzer (LMMS#4950)
Browse files Browse the repository at this point in the history
Replace old spectrum analyzer by new one with higher resolution and
many new features.

Resolves LMMS#2847.
  • Loading branch information
he29-net authored and JohannesLorenz committed Jul 17, 2019
1 parent 3e902e2 commit 9734e27
Show file tree
Hide file tree
Showing 42 changed files with 5,327 additions and 749 deletions.
2 changes: 2 additions & 0 deletions include/EffectControlDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class LMMS_EXPORT EffectControlDialog : public QWidget, public ModelView
EffectControlDialog( EffectControls * _controls );
virtual ~EffectControlDialog();

virtual bool isResizable() const {return false;}


signals:
void closed();
Expand Down
1 change: 0 additions & 1 deletion include/SubWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include <QGraphicsDropShadowEffect>
#include <QMdiSubWindow>
#include <QLabel>
#include <QPainter>
#include <QPushButton>
#include <QString>

Expand Down
94 changes: 64 additions & 30 deletions include/fft_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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.
*
* @return -1 on error, 0 on success
*/
float LMMS_EXPORT maximum(const float *abs_spectrum, unsigned int spec_size);
float LMMS_EXPORT maximum(const std::vector<float> &abs_spectrum);


/** 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.
*
* @return -1 on error
*/
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);


/** Check if the spectrum contains any non-zero value.
*
* returns -1 on error
* @return 1 if spectrum contains any non-zero value
* @return 0 otherwise
*/
float LMMS_EXPORT maximum( float * _abs_spectrum, unsigned int _spec_size );
int LMMS_EXPORT notEmpty(const std::vector<float> &spectrum);


/* apply hanning or hamming window to channel
/** Precompute a window function for later real-time use.
* Set normalized to false if you do not want to apply amplitude correction.
*
* returns -1 on error
* @return -1 on error
*/
int LMMS_EXPORT hanming( float * _timebuffer, int _length, WINDOWS _type );
int LMMS_EXPORT precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool normalized = true);

/* 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!

/** 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
* @return 0 on success, else -1
*/
int LMMS_EXPORT absspec( fftwf_complex * _complex_buffer, float * _absspec_buffer,
int _compl_length );
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
/** 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
* @return 0 on success, else -1
*/
int LMMS_EXPORT compressbands( float * _absspec_buffer, float * _compressedband,
int _num_old, int _num_new, int _bottom, int _top );
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);

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 power of finite time sequence.
* Take care num_values is length of timesignal[].
*
* returns power on success, else -1
* @return power on success, else -1
*/
float LMMS_EXPORT signalpower(float *timesignal, int num_values);
float LMMS_EXPORT signalpower(const float *timesignal, int num_values);

#endif
75 changes: 75 additions & 0 deletions plugins/SpectrumAnalyzer/Analyzer.cpp
Original file line number Diff line number Diff line change
@@ -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));
}
}

Original file line number Diff line number Diff line change
@@ -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
*
Expand All @@ -22,32 +24,30 @@
*
*/

#ifndef _SPECTRUM_ANALYZER_CONTROL_DIALOG_H
#define _SPECTRUM_ANALYZER_CONTROL_DIALOG_H

#include "EffectControlDialog.h"

#ifndef ANALYZER_H
#define ANALYZER_H

class SpectrumAnalyzerControls;
#include "Effect.h"
#include "SaControls.h"
#include "SaProcessor.h"


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() {};

private:
virtual void paintEvent( QPaintEvent* event );
bool processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) override;
EffectControls *controls() override {return &m_controls;}

SpectrumAnalyzerControls* m_controls;
SaProcessor *getProcessor() {return &m_processor;}

QPixmap m_logXAxis;
QPixmap m_logYAxis;
private:
SaProcessor m_processor;
SaControls m_controls;
};

} ;
#endif // ANALYZER_H

#endif
3 changes: 2 additions & 1 deletion plugins/SpectrumAnalyzer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 19 additions & 0 deletions plugins/SpectrumAnalyzer/README.md
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 9734e27

Please sign in to comment.