diff --git a/data/projects/templates/default.mpt b/data/projects/templates/default.mpt index bebd41fe3bb..677726c64e5 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 ff3b934f08b..b504f93cb17 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -121,6 +121,7 @@ class LMMS_EXPORT DataFile : public QDomDocument 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 912bcdc0977..5a7b412a621 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 @@ class LMMS_EXPORT Oscillator 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 @@ class LMMS_EXPORT 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 * _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 @@ class LMMS_EXPORT Oscillator 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 00000000000..dcdad8dc4f5 --- /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 032604c8cf3..a70251a2533 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 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject } + 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 8c05bb9956a..a6d5a05704f 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 815fa350589..6c84874634e 100644 --- a/plugins/triple_oscillator/TripleOscillator.h +++ b/plugins/triple_oscillator/TripleOscillator.h @@ -60,6 +60,7 @@ class OscillatorObject : public Model FloatModel m_stereoPhaseDetuningModel; IntModel m_waveShapeModel; IntModel m_modulationAlgoModel; + BoolModel m_useWaveTableModel; SampleBuffer* m_sampleBuffer; float m_volumeLeft; @@ -71,6 +72,7 @@ class OscillatorObject : public Model // 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 @@ class TripleOscillatorView : public InstrumentViewFixedSize 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 @@ class TripleOscillatorView : public InstrumentViewFixedSize m_phaseOffsetKnob( po ), m_stereoPhaseDetuningKnob( spd ), m_userWaveButton( uwb ), - m_waveShapeBtnGrp( wsbg ) + m_waveShapeBtnGrp( wsbg ), + m_multiBandWaveTableButton( wt ) { } OscillatorKnobs() @@ -185,6 +190,7 @@ class TripleOscillatorView : public InstrumentViewFixedSize 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 00000000000..b65fdbd139a Binary files /dev/null and b/plugins/triple_oscillator/wavetable_active.png differ diff --git a/plugins/triple_oscillator/wavetable_inactive.png b/plugins/triple_oscillator/wavetable_inactive.png new file mode 100644 index 00000000000..f62b28f4b9e Binary files /dev/null and b/plugins/triple_oscillator/wavetable_inactive.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a63a28d5fc..09f490bc64e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -183,6 +183,7 @@ SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${LILV_LIBRARIES} ${SAMPLERATE_LIBRARIES} ${SNDFILE_LIBRARIES} + ${FFTW3F_LIBRARIES} ${EXTRA_LIBRARIES} rpmalloc ) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 1ca63eb92a5..11111c573d0 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -69,7 +69,8 @@ const std::vector 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. @@ -1734,6 +1735,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 214e7b59e2e..867260c2279 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 ecd1d6ea791..8999b9d4b6e 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,72 +25,288 @@ #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" + +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, - 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 ) +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, const ch_cnt_t _chnl ) @@ -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 e99b2d0a81c..d4d3aedcc60 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");