From 6f8c6dba82e7ffdefe01e30fda05abd7d4cc3081 Mon Sep 17 00:00:00 2001 From: Martin Pavelek Date: Sun, 4 Jul 2021 13:14:59 +0200 Subject: [PATCH] Alias-free oscillators (#5826) Add a band-limited, alias-free wavetable oscillator option to the `Oscillator` class. Use it by default for Triple Oscillator. Savefiles which do not have this feature enabled (e.g. old savefiles) will be loaded without this feature to keep the sound consistent. Original author: @curlymorphic. Fixed: @he29-net. --- data/projects/templates/default.mpt | 2 +- include/DataFile.h | 1 + include/Oscillator.h | 131 +++++- include/OscillatorConstants.h | 57 +++ include/SampleBuffer.h | 5 + .../triple_oscillator/TripleOscillator.cpp | 35 +- plugins/triple_oscillator/TripleOscillator.h | 10 +- .../triple_oscillator/wavetable_active.png | Bin 0 -> 1251 bytes .../triple_oscillator/wavetable_inactive.png | Bin 0 -> 1116 bytes src/CMakeLists.txt | 1 + src/core/DataFile.cpp | 25 +- src/core/Engine.cpp | 7 + src/core/Oscillator.cpp | 372 +++++++++++++++--- src/core/SampleBuffer.cpp | 9 + 14 files changed, 580 insertions(+), 75 deletions(-) create mode 100644 include/OscillatorConstants.h create mode 100644 plugins/triple_oscillator/wavetable_active.png create mode 100644 plugins/triple_oscillator/wavetable_inactive.png diff --git a/data/projects/templates/default.mpt b/data/projects/templates/default.mpt index bebd41fe3..677726c64 100644 --- a/data/projects/templates/default.mpt +++ b/data/projects/templates/default.mpt @@ -7,7 +7,7 @@ - + diff --git a/include/DataFile.h b/include/DataFile.h index ff3b934f0..b504f93cb 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -121,6 +121,7 @@ private: void upgrade_noHiddenClipNames(); void upgrade_automationNodes(); void upgrade_extendedNoteRange(); + void upgrade_defaultTripleOscillatorHQ(); // List of all upgrade methods static const std::vector UPGRADE_METHODS; diff --git a/include/Oscillator.h b/include/Oscillator.h index 912bcdc09..5a7b412a6 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -2,6 +2,7 @@ * Oscillator.h - declaration of class Oscillator * * Copyright (c) 2004-2014 Tobias Doerffel + * 2018 Dave French * * This file is part of LMMS - https://lmms.io * @@ -25,16 +26,20 @@ #ifndef OSCILLATOR_H #define OSCILLATOR_H -#include "lmmsconfig.h" - +#include +#include #include #ifdef LMMS_HAVE_STDLIB_H #include #endif -#include "SampleBuffer.h" +#include "Engine.h" #include "lmms_constants.h" +#include "lmmsconfig.h" +#include "Mixer.h" +#include "OscillatorConstants.h" +#include "SampleBuffer.h" class IntModel; @@ -53,8 +58,10 @@ public: ExponentialWave, WhiteNoise, UserDefinedWave, - NumWaveShapes - } ; + NumWaveShapes, //!< Number of all available wave shapes + FirstWaveShapeTable = TriangleWave, //!< First wave shape that has a pre-generated table + NumWaveShapeTables = WhiteNoise - FirstWaveShapeTable, //!< Number of band-limited wave shapes to be generated + }; enum ModulationAlgos { @@ -67,29 +74,35 @@ public: } ; - Oscillator( const IntModel * _wave_shape_model, - const IntModel * _mod_algo_model, - const float & _freq, - const float & _detuning, - const float & _phase_offset, - const float & _volume, - Oscillator * _m_subOsc = NULL ); + Oscillator( const IntModel *wave_shape_model, + const IntModel *mod_algo_model, + const float &freq, + const float &detuning_div_samplerate, + const float &phase_offset, + const float &volume, + Oscillator *m_subOsc = nullptr); virtual ~Oscillator() { delete m_subOsc; } + static void waveTableInit(); + static void destroyFFTPlans(); + static void generateAntiAliasUserWaveTable(SampleBuffer* sampleBuffer); + + inline void setUseWaveTable(bool n) + { + m_useWaveTable = n; + } inline void setUserWave( const SampleBuffer * _wave ) { m_userWave = _wave; } - void update( sampleFrame * _ab, const fpp_t _frames, - const ch_cnt_t _chnl ); + void update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator = false); // now follow the wave-shape-routines... - static inline sample_t sinSample( const float _sample ) { return sinf( _sample * F_2PI ); @@ -153,18 +166,104 @@ public: return m_userWave->userWaveSample( _sample ); } + struct wtSampleControl { + float frame; + f_cnt_t f1; + f_cnt_t f2; + int band; + }; + + inline wtSampleControl getWtSampleControl(const float sample) const + { + wtSampleControl control; + control.frame = sample * OscillatorConstants::WAVETABLE_LENGTH; + control.f1 = static_cast(control.frame) % OscillatorConstants::WAVETABLE_LENGTH; + if (control.f1 < 0) + { + control.f1 += OscillatorConstants::WAVETABLE_LENGTH; + } + control.f2 = control.f1 < OscillatorConstants::WAVETABLE_LENGTH - 1 ? + control.f1 + 1 : + 0; + control.band = waveTableBandFromFreq(m_freq * m_detuning_div_samplerate * Engine::mixer()->processingSampleRate()); + return control; + } + + inline sample_t wtSample( + const sample_t table[OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT][OscillatorConstants::WAVETABLE_LENGTH], + const float sample) const + { + assert(table != nullptr); + wtSampleControl control = getWtSampleControl(sample); + return linearInterpolate(table[control.band][control.f1], + table[control.band][control.f2], fraction(control.frame)); + } + + inline sample_t wtSample(const std::unique_ptr& table, const float sample) const + { + assert(table != nullptr); + wtSampleControl control = getWtSampleControl(sample); + return linearInterpolate((*table)[control.band][control.f1], + (*table)[control.band][control.f2], fraction(control.frame)); + } + + inline sample_t wtSample(sample_t **table, const float sample) const + { + assert(table != nullptr); + wtSampleControl control = getWtSampleControl(sample); + return linearInterpolate(table[control.band][control.f1], + table[control.band][control.f2], fraction(control.frame)); + } + + static inline int waveTableBandFromFreq(float freq) + { + // Frequency bands are indexed relative to default MIDI key frequencies. + // I.e., 440 Hz (A4, key 69): 69 + 12 * log2(1) = 69 + // To always avoid aliasing, ceil() is used instead of round(). It ensures that the nearest wavetable with + // lower than optimal number of harmonics is used when exactly matching wavetable is not available. + int band = (69 + static_cast(std::ceil(12.0f * std::log2(freq / 440.0f)))) / OscillatorConstants::SEMITONES_PER_TABLE; + // Limit the returned value to a valid wavetable index range. + // (qBound would bring Qt into the audio code, which not a preferable option due to realtime safety. + // C++17 std::clamp() could be used in the future.) + return band <= 1 ? 1 : band >= OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT-1 ? OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT-1 : band; + } + + static inline float freqFromWaveTableBand(int band) + { + return 440.0f * std::pow(2.0f, (band * OscillatorConstants::SEMITONES_PER_TABLE - 69.0f) / 12.0f); + } private: const IntModel * m_waveShapeModel; const IntModel * m_modulationAlgoModel; const float & m_freq; - const float & m_detuning; + const float & m_detuning_div_samplerate; const float & m_volume; const float & m_ext_phaseOffset; Oscillator * m_subOsc; float m_phaseOffset; float m_phase; const SampleBuffer * m_userWave; + bool m_useWaveTable; + // There are many update*() variants; the modulator flag is stored as a member variable to avoid + // adding more explicit parameters to all of them. Can be converted to a parameter if needed. + bool m_isModulator; + + /* Multiband WaveTable */ + static sample_t s_waveTables[WaveShapes::NumWaveShapeTables][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT][OscillatorConstants::WAVETABLE_LENGTH]; + static fftwf_plan s_fftPlan; + static fftwf_plan s_ifftPlan; + static fftwf_complex * s_specBuf; + static float s_sampleBuffer[OscillatorConstants::WAVETABLE_LENGTH]; + + static void generateSawWaveTable(int bands, sample_t* table, int firstBand = 1); + static void generateTriangleWaveTable(int bands, sample_t* table, int firstBand = 1); + static void generateSquareWaveTable(int bands, sample_t* table, int firstBand = 1); + static void generateFromFFT(int bands, sample_t* table); + static void generateWaveTables(); + static void createFFTPlans(); + + /* End Multiband wavetable */ void updateNoSub( sampleFrame * _ab, const fpp_t _frames, diff --git a/include/OscillatorConstants.h b/include/OscillatorConstants.h new file mode 100644 index 000000000..dcdad8dc4 --- /dev/null +++ b/include/OscillatorConstants.h @@ -0,0 +1,57 @@ +/* + * OscillatorConstants.h - declaration of constants used in Oscillator and SampleBuffer + * for band limited wave tables + * + * Copyright (c) 2018 Dave French + * + * 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 OSCILLATORCONSTANTS_H +#define OSCILLATORCONSTANTS_H + +#include + +#include "lmms_basics.h" + +namespace OscillatorConstants +{ + // Limit wavetables to the audible audio spectrum + const int MAX_FREQ = 20000; + // Minimum size of table to have all audible bands for midi note 1 (i.e. 20 000 Hz / 8.176 Hz) + constexpr int WAVETABLE_LENGTH = static_cast(MAX_FREQ / 8.176); + + //SEMITONES_PER_TABLE, the smaller the value the smoother the harmonics change on frequency sweeps + // with the trade off of increased memory requirements to store the wave tables + // require memory = NumberOfWaveShapes*WAVETABLE_LENGTH*(MidiNoteCount/SEMITONES_PER_TABLE)*BytePerSample_t + // 7*2446*(128/1)*4 = 8766464 bytes + const int SEMITONES_PER_TABLE = 1; + const int WAVE_TABLES_PER_WAVEFORM_COUNT = 128 / SEMITONES_PER_TABLE; + + // There is some ambiguity around the use of "wavetable", "wavetable synthesis" or related terms. + // The following meanings and definitions were selected for use in the Oscillator class: + // - wave shape: abstract and precise definition of the graph associated with a given type of wave; + // - waveform: digital representations the wave shape, a set of waves optimized for use at varying pitches; + // - wavetable: a table containing one period of a wave, with frequency content optimized for a specific pitch. + typedef std::array wavetable_t; + typedef std::array waveform_t; +}; + + +#endif // OSCILLATORCONSTANTS_H diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 032604c8c..a70251a25 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -26,6 +26,7 @@ #ifndef SAMPLE_BUFFER_H #define SAMPLE_BUFFER_H +#include #include #include @@ -36,6 +37,7 @@ #include "lmms_basics.h" #include "lmms_math.h" #include "shared_object.h" +#include "OscillatorConstants.h" #include "MemoryManager.h" @@ -273,6 +275,9 @@ public: } + std::unique_ptr m_userAntiAliasWaveTable; + + public slots: void setAudioFile(const QString & audioFile); void loadFromBase64(const QString & data); diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index 8c05bb995..a6d5a0570 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -87,6 +87,7 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : m_modulationAlgoModel( Oscillator::SignalMix, 0, Oscillator::NumModulationAlgos-1, this, tr( "Modulation type %1" ).arg( _idx+1 ) ), + m_useWaveTableModel(true), m_sampleBuffer( new SampleBuffer ), m_volumeLeft( 0.0f ), @@ -94,7 +95,8 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : m_detuningLeft( 0.0f ), m_detuningRight( 0.0f ), m_phaseOffsetLeft( 0.0f ), - m_phaseOffsetRight( 0.0f ) + m_phaseOffsetRight( 0.0f ), + m_useWaveTable( true ) { // Connect knobs with Oscillators' inputs connect( &m_volumeModel, SIGNAL( dataChanged() ), @@ -120,6 +122,9 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : this, SLOT( updatePhaseOffsetRight() ), Qt::DirectConnection ); connect( &m_stereoPhaseDetuningModel, SIGNAL( dataChanged() ), this, SLOT( updatePhaseOffsetLeft() ), Qt::DirectConnection ); + connect ( &m_useWaveTableModel, SIGNAL(dataChanged()), + this, SLOT( updateUseWaveTable())); + updatePhaseOffsetLeft(); updatePhaseOffsetRight(); @@ -206,6 +211,11 @@ void OscillatorObject::updatePhaseOffsetRight() m_phaseOffsetRight = m_phaseOffsetModel.value() / 360.0f; } +void OscillatorObject::updateUseWaveTable() +{ + m_useWaveTable = m_useWaveTableModel.value(); +} + @@ -253,6 +263,8 @@ void TripleOscillator::saveSettings( QDomDocument & _doc, QDomElement & _this ) "wavetype" + is ); m_osc[i]->m_modulationAlgoModel.saveSettings( _doc, _this, "modalgo" + QString::number( i+1 ) ); + m_osc[i]->m_useWaveTableModel.saveSettings( _doc, _this, + "useWaveTable" + QString::number (i+1 ) ); _this.setAttribute( "userwavefile" + is, m_osc[i]->m_sampleBuffer->audioFile() ); } @@ -279,6 +291,8 @@ void TripleOscillator::loadSettings( const QDomElement & _this ) is ); m_osc[i]->m_modulationAlgoModel.loadSettings( _this, "modalgo" + QString::number( i+1 ) ); + m_osc[i]->m_useWaveTableModel.loadSettings( _this, + "useWaveTable" + QString::number (i+1 ) ); m_osc[i]->m_sampleBuffer->setAudioFile( _this.attribute( "userwavefile" + is ) ); } @@ -316,6 +330,7 @@ void TripleOscillator::playNote( NotePlayHandle * _n, m_osc[i]->m_detuningLeft, m_osc[i]->m_phaseOffsetLeft, m_osc[i]->m_volumeLeft ); + oscs_l[i]->setUseWaveTable(m_osc[i]->m_useWaveTable); oscs_r[i] = new Oscillator( &m_osc[i]->m_waveShapeModel, &m_osc[i]->m_modulationAlgoModel, @@ -323,6 +338,7 @@ void TripleOscillator::playNote( NotePlayHandle * _n, m_osc[i]->m_detuningRight, m_osc[i]->m_phaseOffsetRight, m_osc[i]->m_volumeRight ); + oscs_r[i]->setUseWaveTable(m_osc[i]->m_useWaveTable); } else { @@ -334,6 +350,7 @@ void TripleOscillator::playNote( NotePlayHandle * _n, m_osc[i]->m_phaseOffsetLeft, m_osc[i]->m_volumeLeft, oscs_l[i + 1] ); + oscs_l[i]->setUseWaveTable(m_osc[i]->m_useWaveTable); oscs_r[i] = new Oscillator( &m_osc[i]->m_waveShapeModel, &m_osc[i]->m_modulationAlgoModel, @@ -342,6 +359,7 @@ void TripleOscillator::playNote( NotePlayHandle * _n, m_osc[i]->m_phaseOffsetRight, m_osc[i]->m_volumeRight, oscs_r[i + 1] ); + oscs_r[i]->setUseWaveTable(m_osc[i]->m_useWaveTable); } oscs_l[i]->setUserWave( m_osc[i]->m_sampleBuffer ); @@ -660,6 +678,15 @@ TripleOscillatorView::TripleOscillatorView( Instrument * _instrument, "usr_shape_inactive" ) ); ToolTip::add( uwb, tr( "User-defined wave" ) ); + PixmapButton * uwt = new PixmapButton( this, NULL ); + uwt->move( 110, btn_y ); + uwt->setActiveGraphic( PLUGIN_NAME::getIconPixmap( + "wavetable_active" ) ); + uwt->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( + "wavetable_inactive" ) ); + uwt->setCheckable(true); + ToolTip::add( uwt, tr( "Use alias-free wavetable oscillators." ) ); + automatableButtonGroup * wsbg = new automatableButtonGroup( this ); @@ -672,8 +699,9 @@ TripleOscillatorView::TripleOscillatorView( Instrument * _instrument, wsbg->addButton( white_noise_btn ); wsbg->addButton( uwb ); + m_oscKnobs[i] = OscillatorKnobs( vk, pk, ck, flk, frk, pok, - spdk, uwb, wsbg ); + spdk, uwb, wsbg, uwt ); } } @@ -711,6 +739,9 @@ void TripleOscillatorView::modelChanged() &t->m_osc[i]->m_stereoPhaseDetuningModel ); m_oscKnobs[i].m_waveShapeBtnGrp->setModel( &t->m_osc[i]->m_waveShapeModel ); + m_oscKnobs[i].m_multiBandWaveTableButton->setModel( + &t->m_osc[i]->m_useWaveTableModel ); + connect( m_oscKnobs[i].m_userWaveButton, SIGNAL( doubleClicked() ), t->m_osc[i], SLOT( oscUserDefWaveDblClick() ) ); diff --git a/plugins/triple_oscillator/TripleOscillator.h b/plugins/triple_oscillator/TripleOscillator.h index 815fa3505..6c8487463 100644 --- a/plugins/triple_oscillator/TripleOscillator.h +++ b/plugins/triple_oscillator/TripleOscillator.h @@ -60,6 +60,7 @@ private: FloatModel m_stereoPhaseDetuningModel; IntModel m_waveShapeModel; IntModel m_modulationAlgoModel; + BoolModel m_useWaveTableModel; SampleBuffer* m_sampleBuffer; float m_volumeLeft; @@ -71,6 +72,7 @@ private: // normalized offset -> x/360 float m_phaseOffsetLeft; float m_phaseOffsetRight; + bool m_useWaveTable; friend class TripleOscillator; friend class TripleOscillatorView; @@ -84,6 +86,7 @@ private slots: void updateDetuningRight(); void updatePhaseOffsetLeft(); void updatePhaseOffsetRight(); + void updateUseWaveTable(); } ; @@ -161,7 +164,8 @@ private: Knob * po, Knob * spd, PixmapButton * uwb, - automatableButtonGroup * wsbg ) : + automatableButtonGroup * wsbg, + PixmapButton * wt) : m_volKnob( v ), m_panKnob( p ), m_coarseKnob( c ), @@ -170,7 +174,8 @@ private: m_phaseOffsetKnob( po ), m_stereoPhaseDetuningKnob( spd ), m_userWaveButton( uwb ), - m_waveShapeBtnGrp( wsbg ) + m_waveShapeBtnGrp( wsbg ), + m_multiBandWaveTableButton( wt ) { } OscillatorKnobs() @@ -185,6 +190,7 @@ private: Knob * m_stereoPhaseDetuningKnob; PixmapButton * m_userWaveButton; automatableButtonGroup * m_waveShapeBtnGrp; + PixmapButton * m_multiBandWaveTableButton; } ; diff --git a/plugins/triple_oscillator/wavetable_active.png b/plugins/triple_oscillator/wavetable_active.png new file mode 100644 index 0000000000000000000000000000000000000000..b65fdbd139a2a12f252b6b6a9eb620fa3cc339e0 GIT binary patch literal 1251 zcmV<91RVQ`P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+T~YWnye@ceP~{p-)>{=h-Wv-4^(q!1PyKKaBMi@5B^KGWjIbwApi>0R!Q=L0boq4t+MVndO z?!5twqOd3IxC+UFYwTX6pClQxjVnWmM6hOzjCK5sojb6*gLkfO*e!X=`xB^NUuLTUlsaV?>2_quK z4U84AxngXN5;4$j^^Z3xkGWy8$8frBoKVw`xBPJ!=(8k;03P9oUYz?wkJ zXDl@Qrr~d+2+xI}$pm9G7`zSlN%)g-8fvYVF3}PvPJ>|XHH|PjbNvb}5JGKYssrCK z+)LdoFT@HM)B$ruf_YhY6RXlyTQYbS=;`9!vbP3VnFat6d@F(>0Ru}QQ3|vevk?Ku z2!1jmXYss(0aE2csBHEk0pvvPzVVLH&}HRajI9Mks06f41Vb5Qd9SD+^AR5pzKAYck3!80mOIt2nq@evvDCSBmspLu(rBr>@8miS;Q_T&v z)};BSEi`MfrIt_DXtwPeYjk7o*R0WGtuy9_o?Wa#Y<8HSzMW|242-cSFm9az6f}3v zmPp=nXRdR$Bt}yxgEi7lPNB{i81$7`8=c%;%>9x#r~X^K@mI{bPThaOoa@w0=AOKL zV6EiwMUg>J@4}AhA`KfG6l%;IbN?p&cy_;Q;D2kV1?daM*W%yWaz72C(YE`F+Yq^* z`G3ytUEqgB)b%nJ_z;BgB(cAT&?C?=)wnt(kH0OTYq3y9BMk|D_~fA==ozKcJsF>lWH_DvPpxdz7TKO^X1}A5@Ua-r&}&z` z>*;%s3#Y|8a>lkpGYZS=3~hOt-CZzZH$LD=hZ%i9eaw5f?j>H&M|!fIrcP1OeBijw z*!TdUr?z8uWp{v`e4W-W(vIYg`4trRU__q0T_lwm-*PX5-1qD~f)BXdBX~qqE_-e# zGoE#OIcb6K_&#doY=BviIKR;CMWmk->#xrwK#qG5R${&pcJ6MG_h5!2i000JJOGiWiX#kl3+m50pU;qFB32;bRa{vGh*8l(w*8xH( zn|J^K00(qQO+^Rg2LcNg5^(?@bN~PVMM*?KR4C75U>F4?0Y(@jEG+E*s#U8n_+epT z|IszU)Ugm_1G=FwHi~9!w%`LS#MnTTW?X<&7tzj;fdwgvsmudsI{>x!8$vgBf&Ksh N002ovPDHLkV1k^(OO5~l literal 0 HcmV?d00001 diff --git a/plugins/triple_oscillator/wavetable_inactive.png b/plugins/triple_oscillator/wavetable_inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..f62b28f4b9e6183b4bbaecd5b0e643c710ce2a69 GIT binary patch literal 1116 zcmV-i1f% zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=KJmgFW3h2L34mVhLLU^&>pIoUy$p9`j3Kdz~n z>6w#F>eObv(~cAaH#XGFI~WXG664If3THswI!WNM zgPOq1XD&3y&2zk+5=O2BPbLH-^tcrF&iIvb2I_q*T_T^BP7^SXHH|a|xp9RTfKXqW z>eP1@_pomK16Tn;oiZm(u=wo=u_@ixmQ2n9KlQ=+V{4Gr6#x=pY(+98AmAg3QY3H8 zMg$!b_!MN$5_kgvQsqim*&ML$0VnYo8-0wXFDoBryjlQ4u|*7MvJ}7yL9u=;B&?yL zqQR+ZRMVtcOOzO*#uzP*cT_R4U~19Kl4UDNQb?L&vXoLzC36aakDLLc`G}X&mMN&<8FJ}^Ij@fso4HCR&zDgTuY;drPM;x7Mr!y zaw{EctxFGGd+gRz&%K<~Xl>gkYV<x>`o14vs(xnuM-{2K#T){xD5hW z&^(y=L?Q4XH<_G-B;^vb&S}$jxc^DQ^5Na&Dme9pv0Vx5!<& zy`fgi{6&!oXzaqFsh5ry8ys3HoXYr3`f}}lHNgLY{yTxX#xmRZ>QD;!+oPpc(R74e zH%<6xva%hIJvEh!(SNGlb1(Wv{V;?b=|zzpJ9{((`@(J}DpkuZd(JESw?=jcx%|<{0=| z>g--s^cPi6$+vu)Q^SjMG?~sByLWngU)rm(?gOa1s@DuIwbLKw{&|tOUr6GqcHeXO z(fVH*=up;uP1F4?0Y(@jEG#T-)v8sq(D`9uVQJ`^VCqsvs7#l@1He2uk7Gi85N;57%s*7l6 i$iRY>#8k$CvmF4=iWzlslS}ph0000 DataFile::UPGRADE_METHODS = { &DataFile::upgrade_1_0_99 , &DataFile::upgrade_1_1_0, &DataFile::upgrade_1_1_91 , &DataFile::upgrade_1_2_0_rc3, &DataFile::upgrade_1_3_0 , &DataFile::upgrade_noHiddenClipNames, - &DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange + &DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange, + &DataFile::upgrade_defaultTripleOscillatorHQ }; // Vector of all versions that have upgrade routines. @@ -1736,6 +1737,28 @@ void DataFile::upgrade_extendedNoteRange() } +/** \brief TripleOscillator switched to using high-quality, alias-free oscillators by default + * + * Older projects were made without this feature and would sound differently if loaded + * with the new default setting. This upgrade routine preserves their old behavior. + */ +void DataFile::upgrade_defaultTripleOscillatorHQ() +{ + QDomNodeList tripleoscillators = elementsByTagName("tripleoscillator"); + for (int i = 0; !tripleoscillators.item(i).isNull(); i++) + { + for (int j = 1; j <= 3; j++) + { + // Only set the attribute if it does not exist (default template has it but reports as 1.2.0) + if (tripleoscillators.item(i).toElement().attribute("useWaveTable" + QString::number(j)) == "") + { + tripleoscillators.item(i).toElement().setAttribute("useWaveTable" + QString::number(j), 0); + } + } + } +} + + void DataFile::upgrade() { // Runs all necessary upgrade methods diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index 214e7b59e..867260c22 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -35,6 +35,7 @@ #include "ProjectJournal.h" #include "Song.h" #include "BandLimitedWave.h" +#include "Oscillator.h" float LmmsCore::s_framesPerTick; Mixer* LmmsCore::s_mixer = NULL; @@ -58,6 +59,8 @@ void LmmsCore::init( bool renderOnly ) emit engine->initProgress(tr("Generating wavetables")); // generate (load from file) bandlimited wavetables BandLimitedWave::generateWaves(); + //initilize oscillators + Oscillator::waveTableInit(); emit engine->initProgress(tr("Initializing data structures")); s_projectJournal = new ProjectJournal; @@ -111,6 +114,10 @@ void LmmsCore::destroy() deleteHelper( &s_song ); delete ConfigManager::inst(); + + // The oscillator FFT plans remain throughout the application lifecycle + // due to being expensive to create, and being used whenever a userwave form is changed + Oscillator::destroyFFTPlans(); } diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index ecd1d6ea7..8999b9d4b 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -2,6 +2,7 @@ * Oscillator.cpp - implementation of powerful oscillator-class * * Copyright (c) 2004-2009 Tobias Doerffel + * 2018 Dave French * * This file is part of LMMS - https://lmms.io * @@ -24,71 +25,287 @@ #include "Oscillator.h" +#include +#if !defined(__MINGW32__) && !defined(__MINGW64__) + #include +#endif + #include "BufferManager.h" #include "Engine.h" #include "Mixer.h" #include "AutomatableModel.h" +#include "fftw3.h" +#include "fft_helpers.h" -Oscillator::Oscillator( const IntModel * _wave_shape_model, - const IntModel * _mod_algo_model, - const float & _freq, - const float & _detuning, - const float & _phase_offset, - const float & _volume, - Oscillator * _sub_osc ) : - m_waveShapeModel( _wave_shape_model ), - m_modulationAlgoModel( _mod_algo_model ), - m_freq( _freq ), - m_detuning( _detuning ), - m_volume( _volume ), - m_ext_phaseOffset( _phase_offset ), - m_subOsc( _sub_osc ), - m_phaseOffset( _phase_offset ), - m_phase( _phase_offset ), - m_userWave( NULL ) +void Oscillator::waveTableInit() +{ + createFFTPlans(); + generateWaveTables(); + // The oscillator FFT plans remain throughout the application lifecycle + // due to being expensive to create, and being used whenever a userwave form is changed + // deleted in main.cpp main() + +} + +Oscillator::Oscillator(const IntModel *wave_shape_model, + const IntModel *mod_algo_model, + const float &freq, + const float &detuning_div_samplerate, + const float &phase_offset, + const float &volume, + Oscillator *sub_osc) : + m_waveShapeModel(wave_shape_model), + m_modulationAlgoModel(mod_algo_model), + m_freq(freq), + m_detuning_div_samplerate(detuning_div_samplerate), + m_volume(volume), + m_ext_phaseOffset(phase_offset), + m_subOsc(sub_osc), + m_phaseOffset(phase_offset), + m_phase(phase_offset), + m_userWave(nullptr), + m_useWaveTable(false), + m_isModulator(false) { } -void Oscillator::update( sampleFrame * _ab, const fpp_t _frames, - const ch_cnt_t _chnl ) +void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator) { - if( m_freq >= Engine::mixer()->processingSampleRate() / 2 ) + if (m_freq >= Engine::mixer()->processingSampleRate() / 2) { - BufferManager::clear( _ab, _frames ); + BufferManager::clear(ab, frames); return; } - if( m_subOsc != NULL ) + // If this oscillator is used to PM or PF modulate another oscillator, take a note. + // The sampling functions will check this variable and avoid using band-limited + // wavetables, since they contain ringing that would lead to unexpected results. + m_isModulator = modulator; + if (m_subOsc != nullptr) { - switch( m_modulationAlgoModel->value() ) + switch (m_modulationAlgoModel->value()) { case PhaseModulation: - updatePM( _ab, _frames, _chnl ); + updatePM(ab, frames, chnl); break; case AmplitudeModulation: - updateAM( _ab, _frames, _chnl ); + updateAM(ab, frames, chnl); break; case SignalMix: - updateMix( _ab, _frames, _chnl ); + updateMix(ab, frames, chnl); break; case SynchronizedBySubOsc: - updateSync( _ab, _frames, _chnl ); + updateSync(ab, frames, chnl); break; case FrequencyModulation: - updateFM( _ab, _frames, _chnl ); + updateFM(ab, frames, chnl); } } else { - updateNoSub( _ab, _frames, _chnl ); + updateNoSub(ab, frames, chnl); } } +void Oscillator::generateSawWaveTable(int bands, sample_t* table, int firstBand) +{ + // sawtooth wave contain both even and odd harmonics + // hence sinewaves are added for all bands + // https://en.wikipedia.org/wiki/Sawtooth_wave + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++) + { + // add offset to the position index to match phase of the non-wavetable saw wave; precompute "/ period" + const float imod = (i - OscillatorConstants::WAVETABLE_LENGTH / 2.f) / OscillatorConstants::WAVETABLE_LENGTH; + for (int n = firstBand; n <= bands; n++) + { + table[i] += (n % 2 ? 1.0f : -1.0f) / n * sinf(F_2PI * n * imod) / F_PI_2; + } + } +} + + +void Oscillator::generateTriangleWaveTable(int bands, sample_t* table, int firstBand) +{ + // triangle waves contain only odd harmonics + // hence sinewaves are added for alternate bands + // https://en.wikipedia.org/wiki/Triangle_wave + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++) + { + for (int n = firstBand | 1; n <= bands; n += 2) + { + table[i] += (n & 2 ? -1.0f : 1.0f) / powf(n, 2.0f) * + sinf(F_2PI * n * i / (float)OscillatorConstants::WAVETABLE_LENGTH) / (F_PI_SQR / 8); + } + } +} + + +void Oscillator::generateSquareWaveTable(int bands, sample_t* table, int firstBand) +{ + // square waves only contain odd harmonics, + // at diffrent levels when compared to triangle waves + // https://en.wikipedia.org/wiki/Square_wave + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++) + { + for (int n = firstBand | 1; n <= bands; n += 2) + { + table[i] += (1.0f / n) * sinf(F_2PI * i * n / OscillatorConstants::WAVETABLE_LENGTH) / (F_PI / 4); + } + } +} + + + +// Expects waveform converted to frequency domain to be present in the spectrum buffer +void Oscillator::generateFromFFT(int bands, sample_t* table) +{ + // Keep only specified number of bands, set the rest to zero. + // Add a +1 offset to the requested number of bands, since the first "useful" frequency falls into bin 1. + // I.e., for bands = 1, keeping just bin 0 (center 0 Hz, +- 4 Hz) makes no sense, it would not produce any tone. + for (int i = bands + 1; i < OscillatorConstants::WAVETABLE_LENGTH * 2 - bands; i++) + { + s_specBuf[i][0] = 0.0f; + s_specBuf[i][1] = 0.0f; + } + //ifft + fftwf_execute(s_ifftPlan); + //normalize and copy to result buffer + normalize(s_sampleBuffer, table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1); +} + +void Oscillator::generateAntiAliasUserWaveTable(SampleBuffer *sampleBuffer) +{ + if (sampleBuffer->m_userAntiAliasWaveTable == NULL) {return;} + + for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i) + { + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i) + { + s_sampleBuffer[i] = sampleBuffer->userWaveSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH); + } + fftwf_execute(s_fftPlan); + Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*(sampleBuffer->m_userAntiAliasWaveTable))[i].data()); + } +} + + + +sample_t Oscillator::s_waveTables + [Oscillator::WaveShapes::NumWaveShapeTables] + [OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT] + [OscillatorConstants::WAVETABLE_LENGTH]; +fftwf_plan Oscillator::s_fftPlan; +fftwf_plan Oscillator::s_ifftPlan; +fftwf_complex * Oscillator::s_specBuf; +float Oscillator::s_sampleBuffer[OscillatorConstants::WAVETABLE_LENGTH]; + + + +void Oscillator::createFFTPlans() +{ + Oscillator::s_specBuf = ( fftwf_complex * ) fftwf_malloc( ( OscillatorConstants::WAVETABLE_LENGTH * 2 + 1 ) * sizeof( fftwf_complex ) ); + Oscillator::s_fftPlan = fftwf_plan_dft_r2c_1d(OscillatorConstants::WAVETABLE_LENGTH, s_sampleBuffer, s_specBuf, FFTW_MEASURE ); + Oscillator::s_ifftPlan = fftwf_plan_dft_c2r_1d(OscillatorConstants::WAVETABLE_LENGTH, s_specBuf, s_sampleBuffer, FFTW_MEASURE); + // initialize s_specBuf content to zero, since the values are used in a condition inside generateFromFFT() + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH * 2 + 1; i++) + { + s_specBuf[i][0] = 0.0f; + s_specBuf[i][1] = 0.0f; + } +} + +void Oscillator::destroyFFTPlans() +{ + fftwf_destroy_plan(s_fftPlan); + fftwf_destroy_plan(s_ifftPlan); + fftwf_free(s_specBuf); +} + +void Oscillator::generateWaveTables() +{ + // Generate tables for simple shaped (constructed by summing sine waves). + // Start from the table that contains the least number of bands, and re-use each table in the following + // iteration, adding more bands in each step and avoiding repeated computation of earlier bands. + typedef void (*generator_t)(int, sample_t*, int); + auto simpleGen = [](WaveShapes shape, generator_t generator) + { + const int shapeID = shape - FirstWaveShapeTable; + int lastBands = 0; + + // Clear the first wave table + std::fill( + std::begin(s_waveTables[shapeID][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1]), + std::end(s_waveTables[shapeID][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1]), + 0.f); + + for (int i = OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1; i >= 0; i--) + { + const int bands = OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i); + generator(bands, s_waveTables[shapeID][i], lastBands + 1); + lastBands = bands; + if (i) + { + std::copy( + s_waveTables[shapeID][i], + s_waveTables[shapeID][i] + OscillatorConstants::WAVETABLE_LENGTH, + s_waveTables[shapeID][i - 1]); + } + } + }; + + // FFT-based wave shapes: make standard wave table without band limit, convert to frequency domain, remove bands + // above maximum frequency and convert back to time domain. + auto fftGen = []() + { + // Generate moogSaw tables + for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i) + { + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i) + { + Oscillator::s_sampleBuffer[i] = moogSawSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH); + } + fftwf_execute(s_fftPlan); + generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable][i]); + } + + // Generate exponential tables + for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i) + { + for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i) + { + s_sampleBuffer[i] = expSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH); + } + fftwf_execute(s_fftPlan); + generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable][i]); + } + }; + +// TODO: Mingw compilers currently do not support std::thread. There are some 3rd-party workarounds available, +// but since threading is not essential in this case, it is easier and more reliable to simply generate +// the wavetables serially. Remove the the check and #else branch once std::thread is well supported. +#if !defined(__MINGW32__) && !defined(__MINGW64__) + std::thread sawThread(simpleGen, WaveShapes::SawWave, generateSawWaveTable); + std::thread squareThread(simpleGen, WaveShapes::SquareWave, generateSquareWaveTable); + std::thread triangleThread(simpleGen, WaveShapes::TriangleWave, generateTriangleWaveTable); + std::thread fftThread(fftGen); + sawThread.join(); + squareThread.join(); + triangleThread.join(); + fftThread.join(); +#else + simpleGen(WaveShapes::SawWave, generateSawWaveTable); + simpleGen(WaveShapes::SquareWave, generateSquareWaveTable); + simpleGen(WaveShapes::TriangleWave, generateTriangleWaveTable); + fftGen(); +#endif +} + + void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, @@ -341,7 +558,7 @@ float Oscillator::syncInit( sampleFrame * _ab, const fpp_t _frames, m_subOsc->update( _ab, _frames, _chnl ); } recalcPhase(); - return( m_freq * m_detuning ); + return m_freq * m_detuning_div_samplerate; } @@ -353,7 +570,7 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { recalcPhase(); - const float osc_coeff = m_freq * m_detuning; + const float osc_coeff = m_freq * m_detuning_div_samplerate; for( fpp_t frame = 0; frame < _frames; ++frame ) { @@ -370,9 +587,9 @@ template void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - m_subOsc->update( _ab, _frames, _chnl ); + m_subOsc->update( _ab, _frames, _chnl, true ); recalcPhase(); - const float osc_coeff = m_freq * m_detuning; + const float osc_coeff = m_freq * m_detuning_div_samplerate; for( fpp_t frame = 0; frame < _frames; ++frame ) { @@ -391,9 +608,9 @@ template void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - m_subOsc->update( _ab, _frames, _chnl ); + m_subOsc->update( _ab, _frames, _chnl, false ); recalcPhase(); - const float osc_coeff = m_freq * m_detuning; + const float osc_coeff = m_freq * m_detuning_div_samplerate; for( fpp_t frame = 0; frame < _frames; ++frame ) { @@ -410,9 +627,9 @@ template void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - m_subOsc->update( _ab, _frames, _chnl ); + m_subOsc->update( _ab, _frames, _chnl, false ); recalcPhase(); - const float osc_coeff = m_freq * m_detuning; + const float osc_coeff = m_freq * m_detuning_div_samplerate; for( fpp_t frame = 0; frame < _frames; ++frame ) { @@ -432,7 +649,7 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, { const float sub_osc_coeff = m_subOsc->syncInit( _ab, _frames, _chnl ); recalcPhase(); - const float osc_coeff = m_freq * m_detuning; + const float osc_coeff = m_freq * m_detuning_div_samplerate; for( fpp_t frame = 0; frame < _frames ; ++frame ) { @@ -453,9 +670,9 @@ template void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - m_subOsc->update( _ab, _frames, _chnl ); + m_subOsc->update( _ab, _frames, _chnl, true ); recalcPhase(); - const float osc_coeff = m_freq * m_detuning; + const float osc_coeff = m_freq * m_detuning_div_samplerate; const float sampleRateCorrection = 44100.0f / Engine::mixer()->processingSampleRate(); @@ -471,10 +688,18 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, template<> -inline sample_t Oscillator::getSample( - const float _sample ) +inline sample_t Oscillator::getSample(const float sample) { - return( sinSample( _sample ) ); + const float current_freq = m_freq * m_detuning_div_samplerate * Engine::mixer()->processingSampleRate(); + + if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ) + { + return sinSample(sample); + } + else + { + return 0; + } } @@ -482,9 +707,16 @@ inline sample_t Oscillator::getSample( template<> inline sample_t Oscillator::getSample( - const float _sample ) + const float _sample ) { - return( triangleSample( _sample ) ); + if (m_useWaveTable && !m_isModulator) + { + return wtSample(s_waveTables[WaveShapes::TriangleWave - FirstWaveShapeTable],_sample); + } + else + { + return triangleSample(_sample); + } } @@ -492,9 +724,16 @@ inline sample_t Oscillator::getSample( template<> inline sample_t Oscillator::getSample( - const float _sample ) + const float _sample ) { - return( sawSample( _sample ) ); + if (m_useWaveTable && !m_isModulator) + { + return wtSample(s_waveTables[WaveShapes::SawWave - FirstWaveShapeTable], _sample); + } + else + { + return sawSample(_sample); + } } @@ -502,9 +741,16 @@ inline sample_t Oscillator::getSample( template<> inline sample_t Oscillator::getSample( - const float _sample ) + const float _sample ) { - return( squareSample( _sample ) ); + if (m_useWaveTable && !m_isModulator) + { + return wtSample(s_waveTables[WaveShapes::SquareWave - FirstWaveShapeTable], _sample); + } + else + { + return squareSample(_sample); + } } @@ -514,7 +760,14 @@ template<> inline sample_t Oscillator::getSample( const float _sample ) { - return( moogSawSample( _sample ) ); + if (m_useWaveTable && !m_isModulator) + { + return wtSample(s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable], _sample); + } + else + { + return moogSawSample(_sample); + } } @@ -524,7 +777,14 @@ template<> inline sample_t Oscillator::getSample( const float _sample ) { - return( expSample( _sample ) ); + if (m_useWaveTable && !m_isModulator) + { + return wtSample(s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable], _sample); + } + else + { + return expSample(_sample); + } } @@ -544,9 +804,15 @@ template<> inline sample_t Oscillator::getSample( const float _sample ) { - return( userWaveSample( _sample ) ); + if (m_useWaveTable && !m_isModulator) + { + return wtSample(m_userWave->m_userAntiAliasWaveTable, _sample); + } + else + { + return userWaveSample(_sample); + } } - diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index e99b2d0a8..d4d3aedcc 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -23,6 +23,7 @@ */ #include "SampleBuffer.h" +#include "Oscillator.h" #include @@ -62,6 +63,7 @@ SampleBuffer::SampleBuffer() : + m_userAntiAliasWaveTable(nullptr), m_audioFile(""), m_origData(nullptr), m_origFrames(0), @@ -352,6 +354,13 @@ void SampleBuffer::update(bool keepSettings) emit sampleUpdated(); + // allocate space for anti-aliased wave table + if (m_userAntiAliasWaveTable == nullptr) + { + m_userAntiAliasWaveTable = std::make_unique(); + } + Oscillator::generateAntiAliasUserWaveTable(this); + if (fileLoadError) { QString title = tr("Fail to open file");