From 9024647f32e756904e9966b840f62b4047f2b78c Mon Sep 17 00:00:00 2001 From: Vesa Date: Mon, 7 Apr 2014 02:54:32 +0300 Subject: [PATCH 1/3] FxMixerView: fix the fx chain UI (so we'll be able to see more than 2/3 of an fx) --- src/gui/FxMixerView.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 6c3e69f15..1d972a01f 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -71,15 +71,15 @@ FxMixerView::FxMixerView() : // Channel area m_channelAreaWidget = new QWidget; - chLayout = new QHBoxLayout(m_channelAreaWidget); - chLayout->setSizeConstraint(QLayout::SetMinimumSize); + chLayout = new QHBoxLayout( m_channelAreaWidget ); + chLayout->setSizeConstraint( QLayout::SetMinimumSize ); chLayout->setSpacing( 0 ); chLayout->setMargin( 0 ); m_channelAreaWidget->setLayout(chLayout); // add master channel - m_fxChannelViews.resize(m->numChannels()); - m_fxChannelViews[0] = new FxChannelView(this, this, 0); + m_fxChannelViews.resize( m->numChannels() ); + m_fxChannelViews[0] = new FxChannelView( this, this, 0 ); FxChannelView * masterView = m_fxChannelViews[0]; ml->addWidget( masterView->m_fxLine, 0, Qt::AlignTop ); @@ -90,7 +90,7 @@ FxMixerView::FxMixerView() : for( int i = 1; i < m_fxChannelViews.size(); ++i ) { m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); - chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); + chLayout->addWidget( m_fxChannelViews[i]->m_fxLine ); } // add the scrolling section to the main layout @@ -98,22 +98,22 @@ FxMixerView::FxMixerView() : class ChannelArea : public QScrollArea { public: - ChannelArea(QWidget * parent, FxMixerView * mv) : - QScrollArea(parent), m_mv(mv) {} + ChannelArea( QWidget * parent, FxMixerView * mv ) : + QScrollArea( parent ), m_mv( mv ) {} ~ChannelArea() {} - virtual void keyPressEvent(QKeyEvent * e) + virtual void keyPressEvent( QKeyEvent * e ) { - m_mv->keyPressEvent(e); + m_mv->keyPressEvent( e ); } private: FxMixerView * m_mv; }; - channelArea = new ChannelArea(this, this); - channelArea->setWidget(m_channelAreaWidget); + channelArea = new ChannelArea( this, this ); + channelArea->setWidget( m_channelAreaWidget ); channelArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); channelArea->setFrameStyle( QFrame::NoFrame ); channelArea->setMinimumWidth( fxLineSize.width() * 6 ); - channelArea->setFixedHeight( fxLineSize.height() + + channelArea->setFixedHeight( fxLineSize.height() + style()->pixelMetric( QStyle::PM_ScrollBarExtent ) ); ml->addWidget(channelArea); @@ -124,10 +124,12 @@ FxMixerView::FxMixerView() : connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); ml->addWidget( newChannelBtn, 0, Qt::AlignTop ); - + // Create EffectRack and set initial index to master channel m_rackView = new EffectRackView( &m->m_fxChannels[0]->m_fxChain, this ); + m_rackView->setFixedSize( 245, fxLineSize.height() ); ml->addWidget( m_rackView, 0, Qt::AlignTop ); + setCurrentFxLine( m_fxChannelViews[0]->m_fxLine ); setLayout( ml ); @@ -344,7 +346,7 @@ void FxMixerView::deleteChannel(int index) -void FxMixerView::moveChannelLeft(int index) +void FxMixerView::moveChannelLeft(int index) { // can't move master or first channel left or last channel right if( index <= 1 || index >= m_fxChannelViews.size() ) return; From e10bbeb84e3a2e7c0a66677d7ca123258006d256 Mon Sep 17 00:00:00 2001 From: Vesa Date: Mon, 7 Apr 2014 22:55:44 +0300 Subject: [PATCH 2/3] Bandlimited wave generation implemented for LMMS, currently only utilized by Monstro --- include/BandLimitedWave.h | 186 +++++++++++++++++++++++++++++++++++ plugins/monstro/Monstro.cpp | 28 +++--- plugins/monstro/Monstro.h | 80 ++++++++++----- src/core/BandLimitedWave.cpp | 141 ++++++++++++++++++++++++++ src/core/main.cpp | 3 + 5 files changed, 402 insertions(+), 36 deletions(-) create mode 100644 include/BandLimitedWave.h create mode 100644 src/core/BandLimitedWave.cpp diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h new file mode 100644 index 000000000..9c994a858 --- /dev/null +++ b/include/BandLimitedWave.h @@ -0,0 +1,186 @@ +/* + * BandLimitedWave.h - helper functions for band-limited + * waveform generation + * + * Copyright (c) 2014 Vesa Kivimäki + * + * 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 BANDLIMITEDWAVE_H +#define BANDLIMITEDWAVE_H + +#include "interpolation.h" +#include "lmms_basics.h" +#include "lmms_math.h" +#include "engine.h" +#include "Mixer.h" + +#define MAXLEN 12 +#define MIPMAPSIZE 1 << ( MAXLEN + 1 ) + + +typedef struct +{ +public: + inline sample_t sampleAt( int _table, int _ph ) + { + return m_data[ ( 1 << _table ) + _ph ]; + } + inline void setSampleAt( int _table, int _ph, sample_t _sample ) + { + m_data[ ( 1 << _table ) + _ph ] = _sample; + } +private: + sample_t m_data [ MIPMAPSIZE ]; +} WaveMipMap; + + +class BandLimitedWave +{ +public: + enum Waveforms + { + BLSaw, + BLSquare, + BLTriangle, + BLMoog, + NumBLWaveforms + }; + + BandLimitedWave() {}; + virtual ~BandLimitedWave() {}; + + /*! \brief This method converts frequency to wavelength. The oscillate function takes wavelength as argument so + * use this to convert your note frequency to wavelength before using it. + */ + static inline float freqToLen( float _f ) + { + return freqToLen( _f, engine::mixer()->processingSampleRate() ); + } + + /*! \brief This method converts frequency to wavelength, but you can use any custom sample rate with it. + */ + static inline float freqToLen( float _f, sample_rate_t _sr ) + { + return static_cast( _sr ) / _f; + } + + /*! \brief This method provides interpolated samples of bandlimited waveforms. + * \param _ph The phase of the sample. + * \param _wavelen The wavelength (length of one cycle, ie. the inverse of frequency) of the wanted oscillation, measured in sample frames + * \param _wave The wanted waveform. Options currently are saw, triangle, square and moog saw. + */ + static inline sample_t oscillate( float _ph, float _wavelen, Waveforms _wave ) + { + // high wavelen/ low freq + if( _wavelen >= 1 << MAXLEN ) + { + const int t = MAXLEN; + const int tlen = 1 << t; + const float ph = fraction( _ph ); + const float lookupf = ph * static_cast( tlen ); + const int lookup = static_cast( lookupf ); + const sample_t s1 = s_waveforms[ _wave ].sampleAt( t, lookup ); + const sample_t s2 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 1 ) % tlen ); + return linearInterpolate( s1, s2, fraction( lookupf ) ); + } + // low wavelen/ high freq + if( _wavelen <= 2.0f ) + { + const int t = 1; + const int tlen = 2; + const float ph = fraction( _ph ); + const float lookupf = ph * static_cast( tlen ); + const int lookup = static_cast( lookupf ); + const sample_t s1 = s_waveforms[ _wave ].sampleAt( t, lookup ); + const sample_t s2 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 1 ) % tlen ); + return linearInterpolate( s1, s2, fraction( lookupf ) ); + } + + // get the next higher tlen + int t = 2; + while( ( 1 << t ) < _wavelen ) { t++; } + + const int tlen = 1 << t; + const float ph = fraction( _ph ); + const float lookupf = ph * static_cast( tlen ); + const int lookup = static_cast( lookupf ); + const sample_t s1 = s_waveforms[ _wave ].sampleAt( t, lookup ); + const sample_t s2 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 1 ) % tlen ); + return linearInterpolate( s1, s2, fraction( lookupf ) ); + + + /*const int tlen1 = 1 << t; + const int tlen2 = 1 << ( t - 1 ); + + const float ph = fraction( _ph ); + const float lookupf = ph * static_cast( tlen1 ); + const int lookup1 = static_cast( lookupf ); + const int lookup2 = static_cast( ph * static_cast( tlen2 ) ); + + const sample_t s1 = linearInterpolate( s_waveforms[ _wave ].sampleAt( t, lookup1 ), + s_waveforms[ _wave ].sampleAt( t, ( lookup1 + 1 ) % tlen1 ), + fraction( lookupf ) ); + const sample_t s2 = s_waveforms[ _wave ].sampleAt( t - 1, lookup2 ); + + const float ip = static_cast( tlen1 - _wavelen ) / static_cast( tlen2 ); + + return linearInterpolate( s1, s2, ip );*/ + }; + + /*! \brief The same as oscillate but uses cosinus interpolation instead of linear. + */ + static inline sample_t oscillateCos( float _ph, float _wavelen, Waveforms _wave ) + { + int t = MAXLEN; + while( ( 1 << t ) > _wavelen ) { t--; } + t = qMax( 1, t ); + + const int tlen = 1 << t; + const float ph = fraction( _ph ); + const int lookup = static_cast( ph * tlen ); + const sample_t s1 = s_waveforms[ _wave ].sampleAt( t, lookup ); + const sample_t s2 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 1 ) % tlen ); + + return cosinusInterpolate( s1, s2, ph ); + }; + + /*! \brief The same as oscillate but without any interpolation. + */ + static inline sample_t oscillateNoip( float _ph, float _wavelen, Waveforms _wave ) + { + int t = MAXLEN; + while( ( 1 << t ) > _wavelen ) { t--; } + t = qMax( 1, t ); + + const int tlen = 1 << t; + const float ph = fraction( _ph ); + const int lookup = static_cast( ph * tlen ); + return s_waveforms[ _wave ].sampleAt( t, lookup ); + }; + + + static void generateWaves(); + + static WaveMipMap s_waveforms [NumBLWaveforms]; +}; + + +#endif diff --git a/plugins/monstro/Monstro.cpp b/plugins/monstro/Monstro.cpp index 465f0b859..fc296c115 100644 --- a/plugins/monstro/Monstro.cpp +++ b/plugins/monstro/Monstro.cpp @@ -97,7 +97,7 @@ MonstroSynth::MonstroSynth( MonstroInstrument * _i, NotePlayHandle * _nph, m_l_last = 0.0f; m_r_last = 0.0f; -// constants for very simple antialias/bandlimiting by amp delta capping +// constants for amp delta capping m_adcap1 = ADCAP1 / m_samplerate; m_adcap2 = ADCAP2 / m_samplerate; } @@ -400,10 +400,10 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) if( o2r_p < 0 ) o2r_p -= floorf( o2r_p ); // multi-wave DC Oscillator - sample_t O2L = oscillate( o2w, o2l_p ); - sample_t O2R = oscillate( o2w, o2r_p ); + sample_t O2L = oscillate( o2w, o2l_p, BandLimitedWave::freqToLen( o2l_f, m_samplerate ) ); + sample_t O2R = oscillate( o2w, o2r_p, BandLimitedWave::freqToLen( o2r_f, m_samplerate ) ); - // do simple alias reduction filtering before volume is touched, by capping amplitude delta + // do amplitude delta cap O2L = qBound( m_osc2l_last - m_adcap1, O2L, m_osc2l_last + m_adcap1 ); O2R = qBound( m_osc2r_last - m_adcap1, O2R, m_osc2r_last + m_adcap1 ); m_osc2l_last = O2L; @@ -454,12 +454,12 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) if( o3r_p < 0 ) o3r_p -= floorf( o3r_p ); // multi-wave DC Oscillator, sub-osc 1 - sample_t O3AL = oscillate( o3w1, o3l_p ); - sample_t O3AR = oscillate( o3w1, o3r_p ); + sample_t O3AL = oscillate( o3w1, o3l_p, BandLimitedWave::freqToLen( o3l_f, m_samplerate ) ); + sample_t O3AR = oscillate( o3w1, o3r_p, BandLimitedWave::freqToLen( o3r_f, m_samplerate ) ); // multi-wave DC Oscillator, sub-osc 2 - sample_t O3BL = oscillate( o3w2, o3l_p ); - sample_t O3BR = oscillate( o3w2, o3r_p ); + sample_t O3BL = oscillate( o3w2, o3l_p, BandLimitedWave::freqToLen( o3l_f, m_samplerate ) ); + sample_t O3BR = oscillate( o3w2, o3r_p, BandLimitedWave::freqToLen( o3r_f, m_samplerate ) ); // calc and modulate sub sub = o3sub; @@ -468,7 +468,7 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) sample_t O3L = linearInterpolate( O3AL, O3BL, sub ); sample_t O3R = linearInterpolate( O3AR, O3BR, sub ); - // do very simple bandlimit filtering by amp delta capping, before volume is touched + // do amp delta capping, before volume is touched O3L = qBound( m_osc3l_last - m_adcap1, O3L, m_osc3l_last + m_adcap1 ); O3R = qBound( m_osc3r_last - m_adcap1, O3R, m_osc3r_last + m_adcap1 ); m_osc3l_last = O3L; @@ -490,7 +490,7 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) m_osc3l_phase = fraction( m_osc3l_phase + 1.0f / ( static_cast( m_samplerate ) / o3l_f ) ); m_osc3r_phase = fraction( m_osc3r_phase + 1.0f / ( static_cast( m_samplerate ) / o3r_f ) ); - // simple bandlimiting + // amp delta caps sample_t L = O1L + O3L + ( omod == MOD_MIX ? O2L : 0.0f ); sample_t R = O1R + O3R + ( omod == MOD_MIX ? O2R : 0.0f ); @@ -591,7 +591,7 @@ void MonstroSynth::renderModulators( fpp_t _frames ) { const f_cnt_t t = f + tfp; const float ph = m_lfo1_phase + lfo1_po; - lfo1_s = oscillate( WAVE_SQRSOFT, ph ); + lfo1_s = oscillate( WAVE_SQRSOFT, ph, lfo1_r ); if( t < m_parent->m_lfo1_att ) lfo1_s *= ( static_cast( t ) / m_parent->m_lfo1_att ); m_lfo1_buf[f] = lfo1_s; m_lfo1_phase += 1.0f / lfo1_r; @@ -613,7 +613,7 @@ void MonstroSynth::renderModulators( fpp_t _frames ) { const f_cnt_t t = f + tfp; const float ph = m_lfo1_phase + lfo1_po; - lfo1_s = oscillate( WAVE_SINABS, ph ); + lfo1_s = oscillate( WAVE_SINABS, ph, lfo1_r ); if( t < m_parent->m_lfo1_att ) lfo1_s *= ( static_cast( t ) / m_parent->m_lfo1_att ); m_lfo1_buf[f] = lfo1_s; m_lfo1_phase += 1.0f / lfo1_r; @@ -708,7 +708,7 @@ void MonstroSynth::renderModulators( fpp_t _frames ) { const f_cnt_t t = f + tfp; const float ph = m_lfo2_phase + lfo2_po; - lfo2_s = oscillate( WAVE_SQRSOFT, ph ); + lfo2_s = oscillate( WAVE_SQRSOFT, ph, lfo2_r ); if( t < m_parent->m_lfo2_att ) lfo2_s *= ( static_cast( t ) / m_parent->m_lfo2_att ); m_lfo2_buf[f] = lfo2_s; m_lfo2_phase += 1.0f / lfo2_r; @@ -730,7 +730,7 @@ void MonstroSynth::renderModulators( fpp_t _frames ) { const f_cnt_t t = f + tfp; const float ph = m_lfo2_phase + lfo2_po; - lfo2_s = oscillate( WAVE_SINABS, ph ); + lfo2_s = oscillate( WAVE_SINABS, ph, lfo2_r ); if( t < m_parent->m_lfo2_att ) lfo2_s *= ( static_cast( t ) / m_parent->m_lfo2_att ); m_lfo2_buf[f] = lfo2_s; m_lfo2_phase += 1.0f / lfo2_r; diff --git a/plugins/monstro/Monstro.h b/plugins/monstro/Monstro.h index 45cf71778..2d87b7bd3 100644 --- a/plugins/monstro/Monstro.h +++ b/plugins/monstro/Monstro.h @@ -37,6 +37,7 @@ #include "combobox.h" #include "Oscillator.h" #include "lmms_math.h" +#include "BandLimitedWave.h" // // UI Macros @@ -66,15 +67,21 @@ #define setwavemodel( name ) \ name .addItem( tr( "Sine wave" ), static_cast( new PluginPixmapLoader( "sin" ) ) ); \ - name .addItem( tr( "Triangle wave" ), static_cast( new PluginPixmapLoader( "tri" ) ) ); \ - name .addItem( tr( "Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ - name .addItem( tr( "Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ - name .addItem( tr( "Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ + name .addItem( tr( "Bandlimited Triangle wave" ), static_cast( new PluginPixmapLoader( "tri" ) ) ); \ + name .addItem( tr( "Bandlimited Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ + name .addItem( tr( "Bandlimited Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ + name .addItem( tr( "Bandlimited Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ + name .addItem( tr( "Bandlimited Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); \ name .addItem( tr( "Soft square wave" ), static_cast( new PluginPixmapLoader( "sqrsoft" ) ) ); \ - name .addItem( tr( "Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); \ - name .addItem( tr( "Abs. sine wave" ), static_cast( new PluginPixmapLoader( "sinabs" ) ) ); \ + name .addItem( tr( "Absolute sine wave" ), static_cast( new PluginPixmapLoader( "sinabs" ) ) ); \ name .addItem( tr( "Exponential wave" ), static_cast( new PluginPixmapLoader( "exp" ) ) ); \ - name .addItem( tr( "White noise" ), static_cast( new PluginPixmapLoader( "noise" ) ) ); + name .addItem( tr( "White noise" ), static_cast( new PluginPixmapLoader( "noise" ) ) ); \ + name .addItem( tr( "Digital Triangle wave" ), static_cast( new PluginPixmapLoader( "tri" ) ) ); \ + name .addItem( tr( "Digital Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ + name .addItem( tr( "Digital Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ + name .addItem( tr( "Digital Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ + name .addItem( tr( "Digital Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); \ + #define setlfowavemodel( name ) \ name .addItem( tr( "Sine wave" ), static_cast( new PluginPixmapLoader( "sin" ) ) ); \ @@ -82,8 +89,8 @@ name .addItem( tr( "Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ name .addItem( tr( "Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ name .addItem( tr( "Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ - name .addItem( tr( "Soft square wave" ), static_cast( new PluginPixmapLoader( "sqrsoft" ) ) ); \ name .addItem( tr( "Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); \ + name .addItem( tr( "Soft square wave" ), static_cast( new PluginPixmapLoader( "sqrsoft" ) ) ); \ name .addItem( tr( "Abs. sine wave" ), static_cast( new PluginPixmapLoader( "sinabs" ) ) ); \ name .addItem( tr( "Exponential wave" ), static_cast( new PluginPixmapLoader( "exp" ) ) ); \ name .addItem( tr( "Random" ), static_cast( new PluginPixmapLoader( "rand" ) ) ); @@ -136,12 +143,20 @@ const int WAVE_TRI = 1; const int WAVE_SAW = 2; const int WAVE_RAMP = 3; const int WAVE_SQR = 4; -const int WAVE_SQRSOFT = 5; -const int WAVE_MOOG = 6; +const int WAVE_MOOG = 5; + +const int WAVE_SQRSOFT = 6; const int WAVE_SINABS = 7; const int WAVE_EXP = 8; const int WAVE_NOISE = 9; -const int NUM_WAVES = 10; + +const int WAVE_TRI_D = 10; +const int WAVE_SAW_D = 11; +const int WAVE_RAMP_D = 12; +const int WAVE_SQR_D = 13; +const int WAVE_MOOG_D = 14; + +const int NUM_WAVES = 15; // modulation enumerators const int MOD_MIX = 0; @@ -156,8 +171,8 @@ const float MIN_FREQ = 18.0f; const float MAX_FREQ = 48000.0f; // constants for amp delta capping - these will be divided by samplerate by the synth -const float ADCAP1 = 44100 / 4; -const float ADCAP2 = 44100 / 4.5; +const float ADCAP1 = 44100 / 2; +const float ADCAP2 = 44100 / 3; class MonstroInstrument; @@ -205,7 +220,7 @@ private: return fastPow( _s, exp ); } - inline sample_t oscillate( int _wave, const float _ph ) + inline sample_t oscillate( int _wave, const float _ph, float _wavelen ) { switch( _wave ) { @@ -213,16 +228,20 @@ private: return Oscillator::sinSample( _ph ); break; case WAVE_TRI: - return Oscillator::triangleSample( _ph ); + //return Oscillator::triangleSample( _ph ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLTriangle ); break; case WAVE_SAW: - return Oscillator::sawSample( _ph ); + //return Oscillator::sawSample( _ph ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSaw ); break; case WAVE_RAMP: - return Oscillator::sawSample( _ph ) * -1.0; + //return Oscillator::sawSample( _ph ) * -1.0; + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSaw ) * -1.0; break; case WAVE_SQR: - return Oscillator::squareSample( _ph ); + //return Oscillator::squareSample( _ph ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSquare ); break; case WAVE_SQRSOFT: { @@ -234,7 +253,8 @@ private: break; } case WAVE_MOOG: - return Oscillator::moogSawSample( _ph ); + //return Oscillator::moogSawSample( _ph ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLMoog ); break; case WAVE_SINABS: return qAbs( Oscillator::sinSample( _ph ) ); @@ -243,9 +263,25 @@ private: return Oscillator::expSample( _ph ); break; case WAVE_NOISE: - default: return Oscillator::noiseSample( _ph ); break; + + case WAVE_TRI_D: + return Oscillator::triangleSample( _ph ); + break; + case WAVE_SAW_D: + return Oscillator::sawSample( _ph ); + break; + case WAVE_RAMP_D: + return Oscillator::sawSample( _ph ) * -1.0; + break; + case WAVE_SQR_D: + return Oscillator::squareSample( _ph ); + break; + case WAVE_MOOG_D: + return Oscillator::moogSawSample( _ph ); + break; + } return 0.0; } @@ -275,10 +311,10 @@ private: sample_t m_osc3l_last; sample_t m_osc3r_last; - + sample_t m_l_last; sample_t m_r_last; - + float m_adcap1; float m_adcap2; }; diff --git a/src/core/BandLimitedWave.cpp b/src/core/BandLimitedWave.cpp new file mode 100644 index 000000000..65cc39119 --- /dev/null +++ b/src/core/BandLimitedWave.cpp @@ -0,0 +1,141 @@ +/* + * BandLimitedWave.h - helper functions for band-limited + * waveform generation + * + * Copyright (c) 2014 Vesa Kivimäki + * + * 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 "BandLimitedWave.h" + + +WaveMipMap BandLimitedWave::s_waveforms[4] = { }; + + +void BandLimitedWave::generateWaves() +{ + int i; + +// saw wave - BLSaw + for( i = 1; i <= MAXLEN; i++ ) + { + const int len = 1 << i; + const double om = 1.0 / len; + double max = 0.0; + + for( int ph = 0; ph < len; ph++ ) + { + int harm = 1; + double s = 0.0f; + do + { + const double amp = -1.0 / static_cast( harm ); + const double a2 = cos( om * harm * F_2PI ); + s += amp * a2 * sin( static_cast( ph * harm ) / static_cast( len ) * F_2PI ); + harm++; + } while( len/harm > 2 ); + s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s ); + max = qMax( max, qAbs( s ) ); + } + // normalize + for( int ph = 0; ph < len; ph++ ) + { + sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max; + s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s ); + } + } + +// square wave - BLSquare + for( i = 1; i <= MAXLEN; i++ ) + { + const int len = 1 << i; + const double om = 1.0 / len; + double max = 0.0; + + for( int ph = 0; ph < len; ph++ ) + { + int harm = 1; + double s = 0.0f; + do + { + const double amp = 1.0 / static_cast( harm ); + const double a2 = cos( om * harm * F_2PI ); + s += amp * a2 * sin( static_cast( ph * harm ) / static_cast( len ) * F_2PI ); + harm += 2; + } while( len/harm > 2 ); + s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s ); + max = qMax( max, qAbs( s ) ); + } + // normalize + for( int ph = 0; ph < len; ph++ ) + { + sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max; + s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s ); + } + } + + +// triangle wave - BLTriangle + for( i = 1; i <= MAXLEN; i++ ) + { + const int len = 1 << i; + //const double om = 1.0 / len; + double max = 0.0; + + for( int ph = 0; ph < len; ph++ ) + { + int harm = 1; + double s = 0.0f; + do + { + const double amp = 1.0 / static_cast( harm * harm ); + //const double a2 = cos( om * harm * F_2PI ); + s += amp * /*a2 **/ sin( ( static_cast( ph * harm ) / static_cast( len ) + + ( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI ); + harm += 2; + } while( len/harm > 2 ); + s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s ); + max = qMax( max, qAbs( s ) ); + } + // normalize + for( int ph = 0; ph < len; ph++ ) + { + sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max; + s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s ); + } + } + + +// moog saw wave - BLMoog +// basically, just add in triangle + 270-phase saw + for( i = 1; i <= MAXLEN; i++ ) + { + const int len = 1 << i; + + for( int ph = 0; ph < len; ph++ ) + { + const int sawph = ( ph + static_cast( len * 0.75 ) ) % len; + const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph ); + const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ); + s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f ); + } + } + +} diff --git a/src/core/main.cpp b/src/core/main.cpp index 92631874b..646b25713 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -65,6 +65,7 @@ #include "ProjectRenderer.h" #include "DataFile.h" #include "song.h" +#include "BandLimitedWave.h" static inline QString baseName( const QString & _file ) { @@ -119,6 +120,8 @@ int main( int argc, char * * argv ) new QCoreApplication( argc, argv ) : new QApplication( argc, argv ) ; + // generate bandlimited wavetables for instruments to use + BandLimitedWave::generateWaves(); Mixer::qualitySettings qs( Mixer::qualitySettings::Mode_HighQuality ); ProjectRenderer::OutputSettings os( 44100, false, 160, From 13237f9c8e3bccca88ac19bf5c9aae3bb195ae61 Mon Sep 17 00:00:00 2001 From: Vesa Date: Tue, 8 Apr 2014 09:48:43 +0300 Subject: [PATCH 3/3] Monstro: use phase delta instead of frequency for deciding which wavetable to use --- include/BandLimitedWave.h | 6 ++++++ plugins/monstro/Monstro.cpp | 38 +++++++++++++++++++++++++++++++------ plugins/monstro/Monstro.h | 6 ++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h index 9c994a858..9971cf200 100644 --- a/include/BandLimitedWave.h +++ b/include/BandLimitedWave.h @@ -81,6 +81,12 @@ public: { return static_cast( _sr ) / _f; } + + /*! \brief This method converts phase delta to wavelength. It assumes a phase scale of 0 to 1. */ + static inline float pdToLen( float _pd ) + { + return 1.0f / _pd; + } /*! \brief This method provides interpolated samples of bandlimited waveforms. * \param _ph The phase of the sample. diff --git a/plugins/monstro/Monstro.cpp b/plugins/monstro/Monstro.cpp index fc296c115..644bf3168 100644 --- a/plugins/monstro/Monstro.cpp +++ b/plugins/monstro/Monstro.cpp @@ -79,6 +79,11 @@ MonstroSynth::MonstroSynth( MonstroInstrument * _i, NotePlayHandle * _nph, m_osc3l_phase = 0.0f; m_osc3r_phase = 0.0f; + m_ph2l_last = 0.0f; + m_ph2r_last = 0.0f; + m_ph3l_last = 0.0f; + m_ph3r_last = 0.0f; + m_env1_phase = 0.0f; m_env2_phase = 0.0f; @@ -282,6 +287,10 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) float o3l_p; float o3r_p; float sub; + + // phase delta calculation vars + float pd_l; + float pd_r; // begin for loop for( f_cnt_t f = 0; f < _frames; f++ ) @@ -399,9 +408,15 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) if( o2l_p < 0 ) o2l_p -= floorf( o2l_p ); if( o2r_p < 0 ) o2r_p -= floorf( o2r_p ); + // phase delta + pd_l = qAbs( o2l_p - m_ph2l_last ); + if( pd_l > 0.5 ) pd_l = 1.0 - pd_l; + pd_r = qAbs( o2r_p - m_ph2r_last ); + if( pd_r > 0.5 ) pd_r = 1.0 - pd_r; + // multi-wave DC Oscillator - sample_t O2L = oscillate( o2w, o2l_p, BandLimitedWave::freqToLen( o2l_f, m_samplerate ) ); - sample_t O2R = oscillate( o2w, o2r_p, BandLimitedWave::freqToLen( o2r_f, m_samplerate ) ); + sample_t O2L = oscillate( o2w, o2l_p, BandLimitedWave::pdToLen( pd_l ) ); + sample_t O2R = oscillate( o2w, o2r_p, BandLimitedWave::pdToLen( pd_r ) ); // do amplitude delta cap O2L = qBound( m_osc2l_last - m_adcap1, O2L, m_osc2l_last + m_adcap1 ); @@ -416,8 +431,11 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) modulatevol( O2R, o2v ) // update osc2 phases + m_ph2l_last = m_osc2l_phase; + m_ph2r_last = m_osc2r_phase; m_osc2l_phase = fraction( m_osc2l_phase + 1.0f / ( static_cast( m_samplerate ) / o2l_f ) ); m_osc2r_phase = fraction( m_osc2r_phase + 1.0f / ( static_cast( m_samplerate ) / o2r_f ) ); + ///////////////////////////// // // @@ -453,13 +471,19 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) if( o3l_p < 0 ) o3l_p -= floorf( o3l_p ); if( o3r_p < 0 ) o3r_p -= floorf( o3r_p ); + // phase delta + pd_l = qAbs( o3l_p - m_ph3l_last ); + if( pd_l > 0.5 ) pd_l = 1.0 - pd_l; + pd_r = qAbs( o3r_p - m_ph3r_last ); + if( pd_r > 0.5 ) pd_r = 1.0 - pd_r; + // multi-wave DC Oscillator, sub-osc 1 - sample_t O3AL = oscillate( o3w1, o3l_p, BandLimitedWave::freqToLen( o3l_f, m_samplerate ) ); - sample_t O3AR = oscillate( o3w1, o3r_p, BandLimitedWave::freqToLen( o3r_f, m_samplerate ) ); + sample_t O3AL = oscillate( o3w1, o3l_p, BandLimitedWave::pdToLen( pd_l ) ); + sample_t O3AR = oscillate( o3w1, o3r_p, BandLimitedWave::pdToLen( pd_r ) ); // multi-wave DC Oscillator, sub-osc 2 - sample_t O3BL = oscillate( o3w2, o3l_p, BandLimitedWave::freqToLen( o3l_f, m_samplerate ) ); - sample_t O3BR = oscillate( o3w2, o3r_p, BandLimitedWave::freqToLen( o3r_f, m_samplerate ) ); + sample_t O3BL = oscillate( o3w2, o3l_p, BandLimitedWave::pdToLen( pd_l ) ); + sample_t O3BR = oscillate( o3w2, o3r_p, BandLimitedWave::pdToLen( pd_r ) ); // calc and modulate sub sub = o3sub; @@ -487,6 +511,8 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) } // update osc3 phases + m_ph3l_last = m_osc3l_phase; + m_ph3r_last = m_osc3r_phase; m_osc3l_phase = fraction( m_osc3l_phase + 1.0f / ( static_cast( m_samplerate ) / o3l_f ) ); m_osc3r_phase = fraction( m_osc3r_phase + 1.0f / ( static_cast( m_samplerate ) / o3r_f ) ); diff --git a/plugins/monstro/Monstro.h b/plugins/monstro/Monstro.h index 2d87b7bd3..4bf5fea0c 100644 --- a/plugins/monstro/Monstro.h +++ b/plugins/monstro/Monstro.h @@ -315,6 +315,12 @@ private: sample_t m_l_last; sample_t m_r_last; + float m_ph2l_last; + float m_ph2r_last; + + float m_ph3l_last; + float m_ph3r_last; + float m_adcap1; float m_adcap2; };