diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 73533eb33..a95abb793 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -543,7 +543,7 @@ BBTCOView { /* Plugins */ -TripleOscillatorView knob { +TripleOscillatorView Knob { color: rgb(1, 32, 64); qproperty-outerColor: rgb(0, 0, 0); qproperty-innerRadius: 2; @@ -554,7 +554,7 @@ TripleOscillatorView knob { } -kickerInstrumentView knob#smallKnob { +kickerInstrumentView Knob#smallKnob { color: #595959; qproperty-outerColor: black; qproperty-innerRadius: 3; @@ -565,7 +565,7 @@ kickerInstrumentView knob#smallKnob { } -kickerInstrumentView knob#largeKnob { +kickerInstrumentView Knob#largeKnob { color: #0c3b89; qproperty-outerColor: #519fff; qproperty-innerRadius: 12.0; @@ -576,7 +576,7 @@ kickerInstrumentView knob#largeKnob { } -AudioFileProcessorView knob { +AudioFileProcessorView Knob { color: rgb(240, 147, 14); qproperty-outerColor: rgb(30, 35, 37); qproperty-innerRadius: 4; @@ -586,7 +586,7 @@ AudioFileProcessorView knob { qproperty-lineWidth: 3; } -organicInstrumentView knob { +organicInstrumentView Knob { color: rgb(124, 207, 98); qproperty-outerColor: rgb(13, 42, 4); qproperty-innerRadius: 2; @@ -596,13 +596,13 @@ organicInstrumentView knob { qproperty-lineWidth: 1.5; } -organicInstrumentView knob#harmKnob { +organicInstrumentView Knob#harmKnob { color: rgb(205, 98, 216); qproperty-outerColor: rgb(18, 4, 18); } -organicInstrumentView knob#fx1Knob, -organicInstrumentView knob#volKnob { +organicInstrumentView Knob#fx1Knob, +organicInstrumentView Knob#volKnob { color: rgb(157, 157, 157); qproperty-outerColor: rgb(37, 37, 37); qproperty-innerRadius: 4; @@ -612,7 +612,7 @@ organicInstrumentView knob#volKnob { qproperty-lineWidth: 2; } -sf2InstrumentView knob { +sf2InstrumentView Knob { color: #ff00ea; qproperty-outerColor: rgb(20, 5, 18); qproperty-innerRadius: 2; @@ -622,7 +622,7 @@ sf2InstrumentView knob { qproperty-lineWidth: 2; } -sfxrInstrumentView knob { +sfxrInstrumentView Knob { color: #000; qproperty-outerColor: rgb(194, 177, 145); qproperty-innerRadius: 2; @@ -630,42 +630,42 @@ sfxrInstrumentView knob { qproperty-lineWidth: 2; } -sfxrInstrumentView knob#envKnob { +sfxrInstrumentView Knob#envKnob { color: #263352; qproperty-outerColor: #4b66a4; } -sfxrInstrumentView knob#freqKnob { +sfxrInstrumentView Knob#freqKnob { color: #1e4a22; qproperty-outerColor: #3c9544; } -sfxrInstrumentView knob#changeKnob { +sfxrInstrumentView Knob#changeKnob { color: #591c1c; qproperty-outerColor: #b23737; } -sfxrInstrumentView knob#sqrKnob { +sfxrInstrumentView Knob#sqrKnob { color: #3b2714; qproperty-outerColor: #724c27; } -sfxrInstrumentView knob#repeatKnob { +sfxrInstrumentView Knob#repeatKnob { color: #292929; qproperty-outerColor: #515151; } -sfxrInstrumentView knob#phaserKnob { +sfxrInstrumentView Knob#phaserKnob { color: #144c4d; qproperty-outerColor: #299899; } -sfxrInstrumentView knob#filterKnob { +sfxrInstrumentView Knob#filterKnob { color: #47224c; qproperty-outerColor: #8e4397; } -opl2instrumentView knob { +opl2instrumentView Knob { color: rgb(128,128,128); qproperty-outerColor: rgb(255,255,255); qproperty-innerRadius: 2; @@ -673,7 +673,7 @@ opl2instrumentView knob { qproperty-lineWidth: 2; } -sidInstrumentView knob { +sidInstrumentView Knob { color: rgb(113,95,80); qproperty-outerColor: rgb( 255,255,255 ); qproperty-innerRadius: 2; @@ -681,7 +681,7 @@ sidInstrumentView knob { qproperty-lineWidth: 2; } -WatsynView knob { +WatsynView Knob { qproperty-innerRadius: 1; qproperty-outerRadius: 7; qproperty-centerPointX: 9.5; @@ -689,17 +689,17 @@ WatsynView knob { qproperty-lineWidth: 2; } -WatsynView knob#aKnob { +WatsynView Knob#aKnob { color: #43b2ff; qproperty-outerColor: #43b2ff; } -WatsynView knob#bKnob { +WatsynView Knob#bKnob { color: #fc5431; qproperty-outerColor: #fc5431; } -WatsynView knob#mixKnob { +WatsynView Knob#mixKnob { color: #43ff82; qproperty-outerColor: #43ff82; qproperty-outerRadius: 13; @@ -707,17 +707,17 @@ WatsynView knob#mixKnob { qproperty-centerPointY: 15.5; } -WatsynView knob#mixenvKnob { +WatsynView Knob#mixenvKnob { color: #43ff82; qproperty-outerColor: #43ff82; } -WatsynView knob#xtalkKnob { +WatsynView Knob#xtalkKnob { color: #fb50fb; qproperty-outerColor: #fb50fb; } -MonstroView knob { +MonstroView Knob { color: #ffffff; qproperty-outerColor: #aaaaaa; qproperty-outerRadius: 9; @@ -727,7 +727,7 @@ MonstroView knob { qproperty-lineWidth: 2.5; } -NesInstrumentView knob { +NesInstrumentView Knob { color: #e7231b; qproperty-outerColor: #fff; qproperty-outerRadius: 11.0; diff --git a/include/Graph.h b/include/Graph.h index 018b0ff0d..9474c354d 100644 --- a/include/Graph.h +++ b/include/Graph.h @@ -137,7 +137,7 @@ public: inline int length() const { - return( m_samples.count() ); + return m_length; } inline const float * samples() const @@ -175,6 +175,7 @@ private: void drawSampleAt( int x, float val ); QVector m_samples; + int m_length; float m_minValue; float m_maxValue; float m_step; diff --git a/include/MixHelpers.h b/include/MixHelpers.h index 4a2f510ee..ec5395a90 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -42,6 +42,9 @@ void add( sampleFrame* dst, const sampleFrame* src, int frames ); /*! \brief Add samples from src multiplied by coeffSrc to dst */ void addMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ); +/*! \brief Add samples from src multiplied by coeffSrc to dst, swap inputs */ +void addSwappedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ); + /*! \brief Add samples from src multiplied by coeffSrc and coeffSrcBuf to dst */ void addMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ); diff --git a/include/RingBuffer.h b/include/RingBuffer.h index d32ef01ed..f601e9aa6 100644 --- a/include/RingBuffer.h +++ b/include/RingBuffer.h @@ -1,5 +1,5 @@ /* - * RingBuffer.h - an effective, thread-safe and flexible implementation of a ringbuffer for LMMS + * RingBuffer.h - an effective and flexible implementation of a ringbuffer for LMMS * * Copyright (c) 2014 Vesa Kivimäki * Copyright (c) 2005-2014 Tobias Doerffel @@ -32,7 +32,7 @@ #include "lmms_math.h" #include "MemoryManager.h" -class RingBuffer : public QObject +class EXPORT RingBuffer : public QObject { Q_OBJECT MM_OPERATORS @@ -179,6 +179,24 @@ public: */ void writeAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level ); +/** \brief Mixes a buffer of sampleframes additively to the ringbuffer at specified position, with + * a specified multiplier applied to the frames, with swapped channels + * \param src Pointer to the source buffer + * \param offset Offset in frames against current position, may *NOT* be negative + * \param length Length of the source buffer, if zero, period size is used - must not be higher than the size of the ringbuffer! + * \param level Multiplier applied to the frames before they're written to the ringbuffer + */ + void writeSwappedAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level ); + +/** \brief Mixes a buffer of sampleframes additively to the ringbuffer at specified position, with + * a specified multiplier applied to the frames, with swapped channels + * \param src Pointer to the source buffer + * \param offset Offset in milliseconds against current position, may *NOT* be negative + * \param length Length of the source buffer, if zero, period size is used + * \param level Multiplier applied to the frames before they're written to the ringbuffer + */ + void writeSwappedAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level ); + protected slots: void updateSamplerate(); @@ -186,14 +204,14 @@ protected slots: private: inline f_cnt_t msToFrames( float ms ) { - return static_cast( ceilf( ms * m_samplerate / 1000 ) ); + return static_cast( ceilf( ms * (float)m_samplerate * 0.001f ) ); } const fpp_t m_fpp; sample_rate_t m_samplerate; - f_cnt_t m_size; + size_t m_size; sampleFrame * m_buffer; - f_cnt_t m_position; + volatile unsigned int m_position; }; #endif diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4f4a668e7..0f98b5d8f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -16,6 +16,7 @@ ADD_SUBDIRECTORY(LadspaEffect) ADD_SUBDIRECTORY(lb302) #ADD_SUBDIRECTORY(lb303) ADD_SUBDIRECTORY(MidiImport) +ADD_SUBDIRECTORY(MultitapEcho) ADD_SUBDIRECTORY(monstro) ADD_SUBDIRECTORY(nes) ADD_SUBDIRECTORY(opl2) diff --git a/plugins/MultitapEcho/CMakeLists.txt b/plugins/MultitapEcho/CMakeLists.txt new file mode 100644 index 000000000..bfd9e6249 --- /dev/null +++ b/plugins/MultitapEcho/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(multitapecho MultitapEcho.cpp MultitapEchoControls.cpp MultitapEchoControlDialog.cpp MOCFILES MultitapEchoControls.h MultitapEchoControlDialog.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") diff --git a/plugins/MultitapEcho/MultitapEcho.cpp b/plugins/MultitapEcho/MultitapEcho.cpp new file mode 100644 index 000000000..8c060b2ea --- /dev/null +++ b/plugins/MultitapEcho/MultitapEcho.cpp @@ -0,0 +1,155 @@ +/* + * MultitapEcho.cpp - a multitap echo delay plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 "MultitapEcho.h" +#include "embed.cpp" + + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT multitapecho_plugin_descriptor = +{ + STRINGIFY( PLUGIN_NAME ), + "Multitap Echo", + QT_TRANSLATE_NOOP( "pluginBrowser", "A multitap echo delay plugin" ), + "Vesa Kivimäki ", + 0x0100, + Plugin::Effect, + new PluginPixmapLoader( "logo" ), + NULL, + NULL +} ; + +} + + +MultitapEchoEffect::MultitapEchoEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ) : + Effect( &multitapecho_plugin_descriptor, parent, key ), + m_controls( this ), + m_buffer( 20100.0f ), + m_sampleRate( Engine::mixer()->processingSampleRate() ), + m_sampleRatio( 1.0f / m_sampleRate ) +{ + m_work = new sampleFrame[ Engine::mixer()->framesPerPeriod() ]; + m_buffer.reset(); + updateFilters( 0, 19 ); +} + + +MultitapEchoEffect::~MultitapEchoEffect() +{ + delete m_work; +} + + +void MultitapEchoEffect::updateFilters( int begin, int end ) +{ + for( int i = begin; i <= end; ++i ) + { + m_filter[i][0].setFc( m_lpFreq[i] * m_sampleRatio ); + m_filter[i][1].setFc( m_lpFreq[i] * m_sampleRatio ); + } +} + + +void MultitapEchoEffect::runFilter( sampleFrame * dst, sampleFrame * src, StereoOnePole & filter, const fpp_t frames ) +{ + for( int f = 0; f < frames; ++f ) + { + dst[f][0] = filter[0].update( src[f][0] ); + dst[f][1] = filter[1].update( src[f][1] ); + } +} + + +bool MultitapEchoEffect::processAudioBuffer( sampleFrame * buf, const fpp_t frames ) +{ + if( !isEnabled() || !isRunning () ) + { + return( false ); + } + + double outSum = 0.0; + const float d = dryLevel(); + const float w = wetLevel(); + + // get processing vars + const int steps = m_controls.m_steps.value(); + const float stepLength = m_controls.m_stepLength.value(); + const float dryGain = dbvToAmp( m_controls.m_dryGain.value() ); + const bool swapInputs = m_controls.m_swapInputs.value(); + + // add dry buffer - never swap inputs for dry + m_buffer.writeAddingMultiplied( buf, 0, frames, dryGain ); + + // swapped inputs? + if( swapInputs ) + { + float offset = stepLength; + for( int i = 0; i < steps; ++i ) // add all steps swapped + { + runFilter( m_work, buf, m_filter[i], frames ); + m_buffer.writeSwappedAddingMultiplied( m_work, offset, frames, m_amp[i] ); + offset += stepLength; + } + } + else + { + float offset = stepLength; + for( int i = 0; i < steps; ++i ) // add all steps swapped + { + runFilter( m_work, buf, m_filter[i], frames ); + m_buffer.writeAddingMultiplied( m_work, offset, frames, m_amp[i] ); + offset += stepLength; + } + } + + // pop the buffer and mix it into output + m_buffer.pop( m_work ); + + for( int f = 0; f < frames; ++f ) + { + buf[f][0] = d * buf[f][0] + w * m_work[f][0]; + buf[f][1] = d * buf[f][1] + w * m_work[f][1]; + outSum += buf[f][0]*buf[f][0] + buf[f][1]*buf[f][1]; + } + + checkGate( outSum / frames ); + + return isRunning(); +} + + +extern "C" +{ + +// necessary for getting instance out of shared lib +Plugin * PLUGIN_EXPORT lmms_plugin_main( Model* parent, void* data ) +{ + return new MultitapEchoEffect( parent, static_cast( data ) ); +} + +} diff --git a/plugins/MultitapEcho/MultitapEcho.h b/plugins/MultitapEcho/MultitapEcho.h new file mode 100644 index 000000000..50ac5a55f --- /dev/null +++ b/plugins/MultitapEcho/MultitapEcho.h @@ -0,0 +1,97 @@ +/* + * MultitapEcho.h - a multitap echo delay plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 MULTITAP_ECHO_H +#define MULTITAP_ECHO_H + +#include "Effect.h" +#include "MultitapEchoControls.h" +#include "ValueBuffer.h" +#include "RingBuffer.h" +#include "lmms_math.h" + +class OnePole +{ +public: + OnePole() + { + m_a0 = 1.0; + m_b1 = 0.0; + m_z1 = 0.0; + } + virtual ~OnePole() {} + + inline void setFc( float fc ) + { + m_b1 = expf( -2.0f * F_PI * fc ); + m_a0 = 1.0f - m_b1; + } + + inline float update( float s ) + { + return m_z1 = s * m_a0 + m_z1 * m_b1; + } + +private: + float m_a0, m_b1, m_z1; +}; + +typedef OnePole StereoOnePole [2]; + +class MultitapEchoEffect : public Effect +{ +public: + MultitapEchoEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ); + virtual ~MultitapEchoEffect(); + virtual bool processAudioBuffer( sampleFrame* buf, const fpp_t frames ); + + virtual EffectControls* controls() + { + return &m_controls; + } + +private: + void updateFilters( int begin, int end ); + void runFilter( sampleFrame * dst, sampleFrame * src, StereoOnePole & filter, const fpp_t frames ); + + MultitapEchoControls m_controls; + + float m_amp [20]; + float m_lpFreq [20]; + + RingBuffer m_buffer; + StereoOnePole m_filter [20]; + + float m_sampleRate; + float m_sampleRatio; + + sampleFrame * m_work; + + friend class MultitapEchoControls; + +}; + + +#endif diff --git a/plugins/MultitapEcho/MultitapEchoControlDialog.cpp b/plugins/MultitapEcho/MultitapEchoControlDialog.cpp new file mode 100644 index 000000000..df235f66e --- /dev/null +++ b/plugins/MultitapEcho/MultitapEchoControlDialog.cpp @@ -0,0 +1,99 @@ +/* + * MultitapEchoControlDialog.cpp - a multitap echo delay plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 + +#include "MultitapEchoControlDialog.h" +#include "MultitapEchoControls.h" +#include "embed.h" +#include "Graph.h" +#include "PixmapButton.h" +#include "ToolTip.h" +#include "LedCheckbox.h" +#include "Knob.h" +#include "TempoSyncKnob.h" +#include "LcdSpinBox.h" + + +MultitapEchoControlDialog::MultitapEchoControlDialog( MultitapEchoControls * controls ) : + EffectControlDialog( controls ) +{ + setAutoFillBackground( true ); + QPalette pal; + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); + setPalette( pal ); + setFixedSize( 245, 300 ); + + // graph widgets + + Graph * ampGraph = new Graph( this, Graph::BarStyle, 204, 105 ); + Graph * lpGraph = new Graph( this, Graph::BarStyle, 204, 105 ); + + ampGraph->move( 30, 10 ); + lpGraph->move( 30, 125 ); + + ampGraph->setModel( & controls->m_ampGraph ); + lpGraph->setModel( & controls->m_lpGraph ); + + pal = QPalette(); + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap("graph_bg") ); + + ampGraph->setAutoFillBackground( true ); + ampGraph->setPalette( pal ); + ampGraph->setGraphColor( QColor( 48, 255, 117 ) ); + ampGraph -> setMaximumSize( 204, 105 ); + + lpGraph->setAutoFillBackground( true ); + lpGraph->setPalette( pal ); + lpGraph->setGraphColor( QColor( 255, 48, 117 ) ); + lpGraph -> setMaximumSize( 204, 105 ); + + // steps spinbox + + LcdSpinBox * steps = new LcdSpinBox( 2, this, "Steps" ); + steps->move( 20, 240 ); + steps->setModel( & controls->m_steps ); + + // knobs + + TempoSyncKnob * stepLength = new TempoSyncKnob( knobBright_26, this ); + stepLength->move( 130, 240 ); + stepLength->setModel( & controls->m_stepLength ); + stepLength->setLabel( tr( "Length" ) ); + stepLength->setHintText( tr( "Step length:" ) + " ", "ms" ); + + Knob * dryGain = new Knob( knobBright_26, this ); + dryGain->move( 180, 240 ); + dryGain->setModel( & controls->m_dryGain ); + dryGain->setLabel( tr( "Dry" ) ); + dryGain->setHintText( tr( "Dry Gain:" ) + " ", "dBV" ); + + // switch led + + LedCheckBox * swapInputs = new LedCheckBox( "Swap inputs", this, tr( "Swap inputs" ), LedCheckBox::Green ); + swapInputs->move( 20, 270 ); + swapInputs->setModel( & controls->m_swapInputs ); + ToolTip::add( swapInputs, tr( "Swap left and right input channel for reflections" ) ); +} diff --git a/plugins/MultitapEcho/MultitapEchoControlDialog.h b/plugins/MultitapEcho/MultitapEchoControlDialog.h new file mode 100644 index 000000000..c0fdf7c3c --- /dev/null +++ b/plugins/MultitapEcho/MultitapEchoControlDialog.h @@ -0,0 +1,44 @@ +/* + * MultitapEchoControlDialog.h - a multitap echo delay plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 MULTITAP_ECHO_CONTROL_DIALOG_H +#define MULTITAP_ECHO_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" + +class MultitapEchoControls; + +class MultitapEchoControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + MultitapEchoControlDialog( MultitapEchoControls * controls ); + virtual ~MultitapEchoControlDialog() + { + } +}; + +#endif diff --git a/plugins/MultitapEcho/MultitapEchoControls.cpp b/plugins/MultitapEcho/MultitapEchoControls.cpp new file mode 100644 index 000000000..d83fd9ec4 --- /dev/null +++ b/plugins/MultitapEcho/MultitapEchoControls.cpp @@ -0,0 +1,175 @@ +/* + * MultitapEchoControls.cpp - a multitap echo delay plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 + +#include "MultitapEchoControls.h" +#include "MultitapEcho.h" +#include "lmms_math.h" +#include "base64.h" + + +MultitapEchoControls::MultitapEchoControls( MultitapEchoEffect * eff ) : + EffectControls( eff ), + m_effect( eff ), + m_steps( 16, 4, 20, this, "Steps" ), + m_stepLength( 100.0f, 1.0f, 1000.0f, 0.1f, 1000.0f, this, "Step length" ), + m_dryGain( 0.0f, -80.0f, 20.0f, 0.1f, this, "Dry gain" ), + m_swapInputs( false, this, "Swap inputs" ), + m_ampGraph( -60.0f, 0.0f, 16, this ), + m_lpGraph( 0.0f, 3.0f, 16, this ) +{ + connect( &m_ampGraph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( ampSamplesChanged( int, int ) ) ); + connect( &m_lpGraph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( lpSamplesChanged( int, int ) ) ); + + connect( &m_steps, SIGNAL( dataChanged() ), this, SLOT( lengthChanged() ) ); + connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); + + setDefaultAmpShape(); + setDefaultLpShape(); +} + + +MultitapEchoControls::~MultitapEchoControls() +{ +} + + +void MultitapEchoControls::saveSettings( QDomDocument & doc, QDomElement & parent ) +{ + m_steps.saveSettings( doc, parent, "steps" ); + m_stepLength.saveSettings( doc, parent, "steplength" ); + m_dryGain.saveSettings( doc, parent, "drygain" ); + m_swapInputs.saveSettings( doc, parent, "swapinputs" ); + + QString ampString; + base64::encode( (const char *) m_ampGraph.samples(), m_ampGraph.length() * sizeof(float), ampString ); + parent.setAttribute( "ampsteps", ampString ); + + QString lpString; + base64::encode( (const char *) m_lpGraph.samples(), m_lpGraph.length() * sizeof(float), lpString ); + parent.setAttribute( "lpsteps", lpString ); +} + + +void MultitapEchoControls::loadSettings( const QDomElement & elem ) +{ + m_steps.loadSettings( elem, "steps" ); + m_stepLength.loadSettings( elem, "steplength" ); + m_dryGain.loadSettings( elem, "drygain" ); + m_swapInputs.loadSettings( elem, "swapinputs" ); + + int size = 0; + char * dst = 0; + + base64::decode( elem.attribute( "ampsteps"), &dst, &size ); + m_ampGraph.setSamples( (float*) dst ); + + base64::decode( elem.attribute( "lpsteps"), &dst, &size ); + m_lpGraph.setSamples( (float*) dst ); + + delete[] dst; +} + + +void MultitapEchoControls::setDefaultAmpShape() +{ + const int length = m_steps.value(); + + float samples [length]; + for( int i = 0; i < length; ++i ) + { + samples[i] = 0.0f; + } + + m_ampGraph.setSamples( &samples[0] ); +} + + +void MultitapEchoControls::setDefaultLpShape() +{ + const int length = m_steps.value(); + + float samples [length]; + for( int i = 0; i < length; ++i ) + { + samples[i] = 3.0f; + } + + m_lpGraph.setSamples( &samples[0] ); +} + + +void MultitapEchoControls::ampSamplesChanged( int begin, int end ) +{ + const float * samples = m_ampGraph.samples(); + for( int i = begin; i <= end; ++i ) + { + m_effect->m_amp[i] = dbvToAmp( samples[i] ); + } +} + + +void MultitapEchoControls::ampResetClicked() +{ + setDefaultAmpShape(); +} + + +void MultitapEchoControls::lpSamplesChanged( int begin, int end ) +{ + //qDebug( "b/e %d - %d", begin, end ); + const float * samples = m_lpGraph.samples(); + for( int i = begin; i <= end; ++i ) + { + m_effect->m_lpFreq[i] = 20.0f * exp10f( samples[i] ); + } + m_effect->updateFilters( begin, end ); +} + + +void MultitapEchoControls::lpResetClicked() +{ + setDefaultLpShape(); +} + + +void MultitapEchoControls::lengthChanged() +{ + const int len = m_steps.value(); + m_ampGraph.setLength( len ); + ampSamplesChanged( 0, len - 1 ); + m_lpGraph.setLength( len ); + lpSamplesChanged( 0, len - 1 ); + m_effect->updateFilters( 0, len - 1 ); +} + + +void MultitapEchoControls::sampleRateChanged() +{ + m_effect->m_sampleRate = Engine::mixer()->processingSampleRate(); + m_effect->m_sampleRatio = 1.0f / m_effect->m_sampleRate; + m_effect->updateFilters( 0, 19 ); +} diff --git a/plugins/MultitapEcho/MultitapEchoControls.h b/plugins/MultitapEcho/MultitapEchoControls.h new file mode 100644 index 000000000..645e66ac4 --- /dev/null +++ b/plugins/MultitapEcho/MultitapEchoControls.h @@ -0,0 +1,90 @@ +/* + * MultitapEchoControls.h - a multitap echo delay plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 MULTITAP_ECHO_CONTROLS_H +#define MULTITAP_ECHO_CONTROLS_H + +#include "EffectControls.h" +#include "MultitapEchoControlDialog.h" +#include "Knob.h" +#include "Graph.h" + + +class MultitapEchoEffect; + +class MultitapEchoControls : public EffectControls +{ + Q_OBJECT +public: + MultitapEchoControls( MultitapEchoEffect * eff ); + virtual ~MultitapEchoControls(); + + virtual void saveSettings( QDomDocument & doc, QDomElement & parent ); + virtual void loadSettings( const QDomElement & elem ); + inline virtual QString nodeName() const + { + return( "multitapechocontrols" ); + } + + void setDefaultAmpShape(); + void setDefaultLpShape(); + + virtual int controlCount() + { + return( 4 ); + } + + virtual EffectControlDialog * createView() + { + return( new MultitapEchoControlDialog( this ) ); + } + +private slots: + void ampSamplesChanged( int, int ); + void ampResetClicked(); + + void lpSamplesChanged( int, int ); + void lpResetClicked(); + + void lengthChanged(); + void sampleRateChanged(); + +private: + MultitapEchoEffect * m_effect; + IntModel m_steps; + TempoSyncKnobModel m_stepLength; + + FloatModel m_dryGain; + BoolModel m_swapInputs; + + graphModel m_ampGraph; + graphModel m_lpGraph; + + friend class MultitapEchoEffect; + friend class MultitapEchoControlDialog; +}; + + +#endif diff --git a/plugins/MultitapEcho/artwork.png b/plugins/MultitapEcho/artwork.png new file mode 100644 index 000000000..ead2ede46 Binary files /dev/null and b/plugins/MultitapEcho/artwork.png differ diff --git a/plugins/MultitapEcho/graph_bg.png b/plugins/MultitapEcho/graph_bg.png new file mode 100644 index 000000000..078637d20 Binary files /dev/null and b/plugins/MultitapEcho/graph_bg.png differ diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index a6e43e05a..513dd40d9 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -124,6 +124,25 @@ void addMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, in } +struct AddSwappedMultipliedOp +{ + AddSwappedMultipliedOp( float coeff ) : m_coeff( coeff ) { } + + void operator()( sampleFrame& dst, const sampleFrame& src ) const + { + dst[0] += src[1] * m_coeff; + dst[1] += src[0] * m_coeff; + } + + const float m_coeff; +}; + +void addSwappedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ) +{ + run<>( dst, src, frames, AddSwappedMultipliedOp(coeffSrc) ); +} + + void addMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ) { for( int f = 0; f < frames; ++f ) diff --git a/src/core/RingBuffer.cpp b/src/core/RingBuffer.cpp index 69456ab00..21ec1f70f 100644 --- a/src/core/RingBuffer.cpp +++ b/src/core/RingBuffer.cpp @@ -1,5 +1,5 @@ /* - * RingBuffer.cpp - an effective, thread-safe and flexible implementation of a ringbuffer for LMMS + * RingBuffer.cpp - an effective and flexible implementation of a ringbuffer for LMMS * * Copyright (c) 2014 Vesa Kivimäki * Copyright (c) 2005-2014 Tobias Doerffel @@ -36,6 +36,7 @@ RingBuffer::RingBuffer( f_cnt_t size ) : m_size( size + m_fpp ) { m_buffer = new sampleFrame[ m_size ]; + memset( m_buffer, 0, m_size * sizeof( sampleFrame ) ); m_position = 0; } @@ -49,6 +50,7 @@ RingBuffer::RingBuffer( float size ) : memset( m_buffer, 0, m_size * sizeof( sampleFrame ) ); m_position = 0; setSamplerateAware( true ); + //qDebug( "m_size %d, m_position %d", m_size, m_position ); } @@ -87,11 +89,11 @@ void RingBuffer::setSamplerateAware( bool b ) { if( b ) { - connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ), Qt::UniqueConnection ); + connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ), Qt::UniqueConnection ); } else { - disconnect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); + disconnect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ) ); } } @@ -118,18 +120,18 @@ void RingBuffer::pop( sampleFrame * dst ) { if( m_position + m_fpp <= m_size ) // we won't go over the edge so we can just memcpy here { - memcpy( dst, m_buffer + ( m_position * sizeof( sampleFrame ) ), m_fpp * sizeof( sampleFrame ) ); - memset( m_buffer + ( m_position * sizeof( sampleFrame ) ), 0, m_fpp * sizeof( sampleFrame ) ); + memcpy( dst, & m_buffer [ m_position ], m_fpp * sizeof( sampleFrame ) ); + memset( & m_buffer[m_position], 0, m_fpp * sizeof( sampleFrame ) ); } else { f_cnt_t first = m_size - m_position; f_cnt_t second = m_fpp - first; - memcpy( dst, m_buffer + ( m_position * sizeof( sampleFrame ) ), first * sizeof( sampleFrame ) ); - memset( m_buffer + ( m_position * sizeof( sampleFrame ) ), 0, first * sizeof( sampleFrame ) ); + memcpy( dst, & m_buffer [ m_position ], first * sizeof( sampleFrame ) ); + memset( & m_buffer [m_position], 0, first * sizeof( sampleFrame ) ); - memcpy( dst + ( first * sizeof( sampleFrame ) ), m_buffer, second * sizeof( sampleFrame ) ); + memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) ); memset( m_buffer, 0, second * sizeof( sampleFrame ) ); } @@ -144,16 +146,16 @@ void RingBuffer::read( sampleFrame * dst, f_cnt_t offset ) if( pos + m_fpp <= m_size ) // we won't go over the edge so we can just memcpy here { - memcpy( dst, m_buffer + ( pos * sizeof( sampleFrame ) ), m_fpp * sizeof( sampleFrame ) ); + memcpy( dst, & m_buffer [pos], m_fpp * sizeof( sampleFrame ) ); } else { f_cnt_t first = m_size - pos; f_cnt_t second = m_fpp - first; - memcpy( dst, m_buffer + ( pos * sizeof( sampleFrame ) ), first * sizeof( sampleFrame ) ); + memcpy( dst, & m_buffer [pos], first * sizeof( sampleFrame ) ); - memcpy( dst + ( first * sizeof( sampleFrame ) ), m_buffer, second * sizeof( sampleFrame ) ); + memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) ); } } @@ -171,16 +173,16 @@ void RingBuffer::read( sampleFrame * dst, f_cnt_t offset, f_cnt_t length ) if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here { - memcpy( dst, m_buffer + ( pos * sizeof( sampleFrame ) ), length * sizeof( sampleFrame ) ); + memcpy( dst, & m_buffer [pos], length * sizeof( sampleFrame ) ); } else { f_cnt_t first = m_size - pos; f_cnt_t second = length - first; - memcpy( dst, m_buffer + ( pos * sizeof( sampleFrame ) ), first * sizeof( sampleFrame ) ); + memcpy( dst, & m_buffer [pos], first * sizeof( sampleFrame ) ); - memcpy( dst + ( first * sizeof( sampleFrame ) ), m_buffer, second * sizeof( sampleFrame ) ); + memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) ); } } @@ -198,16 +200,16 @@ void RingBuffer::write( sampleFrame * src, f_cnt_t offset, f_cnt_t length ) if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here { - memcpy( m_buffer + ( pos * sizeof( sampleFrame ) ), src, length * sizeof( sampleFrame ) ); + memcpy( & m_buffer [pos], src, length * sizeof( sampleFrame ) ); } else { f_cnt_t first = m_size - pos; f_cnt_t second = length - first; - memcpy( m_buffer + ( pos * sizeof( sampleFrame ) ), src, first * sizeof( sampleFrame ) ); + memcpy( & m_buffer [pos], src, first * sizeof( sampleFrame ) ); - memcpy( m_buffer, src + ( first * sizeof( sampleFrame ) ), second * sizeof( sampleFrame ) ); + memcpy( m_buffer, & src [first], second * sizeof( sampleFrame ) ); } } @@ -225,16 +227,16 @@ void RingBuffer::writeAdding( sampleFrame * src, f_cnt_t offset, f_cnt_t length if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here { - MixHelpers::add( m_buffer + ( pos * sizeof( sampleFrame ) ), src, length ); + MixHelpers::add( & m_buffer [pos], src, length ); } else { f_cnt_t first = m_size - pos; f_cnt_t second = length - first; - MixHelpers::add( m_buffer + ( pos * sizeof( sampleFrame ) ), src, first ); + MixHelpers::add( & m_buffer[pos], src, first ); - MixHelpers::add( m_buffer, src + ( first * sizeof( sampleFrame ) ), second ); + MixHelpers::add( m_buffer, & src[first], second ); } } @@ -248,27 +250,56 @@ void RingBuffer::writeAdding( sampleFrame * src, float offset, f_cnt_t length ) void RingBuffer::writeAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level ) { const f_cnt_t pos = ( m_position + offset ) % m_size; + //qDebug( "pos %d m_pos %d ofs %d siz %d", pos, m_position, offset, m_size ); if( length == 0 ) { length = m_fpp; } if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here { - MixHelpers::addMultiplied( m_buffer + ( pos * sizeof( sampleFrame ) ), src, level, length ); + MixHelpers::addMultiplied( & m_buffer[pos], src, level, length ); } else { f_cnt_t first = m_size - pos; f_cnt_t second = length - first; - MixHelpers::addMultiplied( m_buffer + ( pos * sizeof( sampleFrame ) ), src, level, first ); + MixHelpers::addMultiplied( & m_buffer[pos], src, level, first ); - MixHelpers::addMultiplied( m_buffer, src + ( first * sizeof( sampleFrame ) ), level, second ); + MixHelpers::addMultiplied( m_buffer, & src [first], level, second ); } } void RingBuffer::writeAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level ) { - writeAddingMultiplied( src, msToFrames( offset ), length, level ); + f_cnt_t ofs = msToFrames( offset ); + writeAddingMultiplied( src, ofs, length, level ); +} + + +void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level ) +{ + const f_cnt_t pos = ( m_position + offset ) % m_size; + if( length == 0 ) { length = m_fpp; } + + if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here + { + MixHelpers::addSwappedMultiplied( & m_buffer [pos], src, level, length ); + } + else + { + f_cnt_t first = m_size - pos; + f_cnt_t second = length - first; + + MixHelpers::addSwappedMultiplied( & m_buffer [pos], src, level, first ); + + MixHelpers::addSwappedMultiplied( m_buffer, & src [first], level, second ); + } +} + + +void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level ) +{ + writeSwappedAddingMultiplied( src, msToFrames( offset ), length, level ); } diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index 009bf4b3b..3fea035cd 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -217,14 +217,17 @@ void Graph::drawLineAt( int _x, int _y, int _lastx ) int xstep = _x > _lastx ? -1 : 1; float ystep = ( lastval - val ) / linelen; + int start = INT_MAX; + int end = 0; // draw a line for ( int i = 0; i < linelen; i++ ) { - int x = (_x + (i * xstep)); // get x value - model()->drawSampleAt( (int)( x * xscale ), val + (i * ystep)); + int x = (_x + (i * xstep)) * xscale; // get x value + model()->drawSampleAt( x, val + (i * ystep)); + start = qMin( start, x ); + end = qMax( end, x ); } - int start = qMin( _x, _x + ( ( linelen - 1 ) * xstep ) ); - int end = qMax( _x, _x + ( ( linelen - 1 ) * xstep ) ); + model()->samplesChanged( start, end ); } @@ -362,7 +365,7 @@ void Graph::paintEvent( QPaintEvent * ) qMax( static_cast( ( minVal - maxVal ) * yscale ) - static_cast( ( (*samps)[i] - maxVal ) * yscale ), 1 ), gcol ); - p.setPen( QPen( m_graphColor, 1 ) ); + p.setPen( QPen( m_graphColor, 1.0 ) ); p.drawLine( 2+static_cast(i*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), @@ -444,6 +447,7 @@ graphModel::graphModel( float _min, float _max, int _length, ::Model * _parent, bool _default_constructed, float _step ) : Model( _parent, tr( "Graph" ), _default_constructed ), m_samples( _length ), + m_length( _length ), m_minValue( _min ), m_maxValue( _max ), m_step( _step ) @@ -482,9 +486,13 @@ void graphModel::setRange( float _min, float _max ) void graphModel::setLength( int _length ) { - if( _length != length() ) + if( _length != m_length ) { - m_samples.resize( _length ); + m_length = _length; + if( m_samples.size() < m_length ) + { + m_samples.resize( m_length ); + } emit lengthChanged(); } }