Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Oscillators -- fixed #5826

Merged
merged 57 commits into from
Jul 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d910df1
Initial Commit.
curlymorphic May 27, 2018
b116e2d
Revised to LMMS coding standards
curlymorphic May 27, 2018
a4b04af
LMMS coding conventions
curlymorphic May 27, 2018
010ac84
Add WT button to triple oscillator
curlymorphic May 28, 2018
e725c95
Band Limted Wavetables for the MoogSaw and Exp waveforms
curlymorphic Jun 3, 2018
e9f5310
fftw3 linking
curlymorphic Jun 3, 2018
fe61c40
Revised to work with the moogsaw and exp waveforms
curlymorphic Jun 9, 2018
20684bf
Oscillator Wave Table, move initilization code to main.cpp
curlymorphic Jun 24, 2018
2d6ebfe
WT oscillator - tidy
curlymorphic Jun 24, 2018
403245e
Triple Oscillator, update tooltip for alias-free wavetables
curlymorphic Jul 5, 2018
ed515d1
Oscillator, Improve generation of wavetables
curlymorphic Jul 5, 2018
87fb793
Oscillator, Improve wave table generation, and add reference links
curlymorphic Jul 5, 2018
aa38d83
Oscillator, refactor create and destory FFT plans
curlymorphic Jul 5, 2018
9be9f76
Oscillator, remove iteration for calculate wavetable bands from frequ…
curlymorphic Jul 6, 2018
d7637f1
Oscillator UserWaveForm
curlymorphic Jul 6, 2018
2a88abc
Oscillator, fix freqFromWaveTableBand(int band)
curlymorphic Jul 6, 2018
42bc7fc
Oscillator, bounds checks applied to the bands, revised wavetable size
curlymorphic Jul 7, 2018
06461b6
Oscillator - band limited user waveforms, Static init
curlymorphic Jul 15, 2018
c06461e
Oscillator added Oscillator::waveTableInit(); to tests/main.cpp
curlymorphic Jul 15, 2018
d0886e0
Oscillator relocate init to Engine.cpp
curlymorphic Jul 15, 2018
1b74605
WT Oscillator code formatting
curlymorphic Aug 25, 2018
44714ca
WTOscillator use dynamic allocation for user waves,
curlymorphic Aug 25, 2018
72b9e11
Merge remote-tracking branch 'upstream/master' into HEAD
he29-net Jun 1, 2020
143b299
Fix memory errors
he29-net Jun 4, 2020
e5b0a5b
Fix normalization of empty waveform (div by 0); reduce minimum thresh…
he29-net Jun 4, 2020
efb1fd9
Fix detuning behavior
he29-net Jun 5, 2020
c710ffd
Multi-threaded wavetable initialization
he29-net Jun 5, 2020
583aa4b
Reduce the cutoff threshold for exp wave as well
he29-net Jun 5, 2020
e7b24ac
Merge remote-tracking branch 'upstream/master' into oscillator-fix
he29-net Dec 5, 2020
f72f082
Merge branch 'master' into oscillator-fix
he29-net Dec 8, 2020
909bf86
Update src/core/SampleBuffer.cpp
he29-net Dec 8, 2020
f8040be
Threading test for mingw; formatting
he29-net Dec 11, 2020
cff107b
Use code for mingw detection from PR 4443
he29-net Dec 11, 2020
1005ce1
Revert "Use code for mingw detection from PR 4443"
he29-net Dec 11, 2020
205c1e8
Revert "Threading test for mingw; formatting"
he29-net Dec 11, 2020
8089afa
Convert user waveform storage to std::unique_ptr and 2D std::array combo
he29-net Dec 11, 2020
15e51b2
Update src/core/Oscillator.cpp
he29-net Jan 9, 2021
cb5d1ea
Implement suggestions from review
he29-net Jan 26, 2021
1ea5da2
Oops, remove unnecessary prefix from constants
he29-net Jan 26, 2021
8a8b445
Implement suggestions from review (batch 1)
he29-net Jun 7, 2021
ea6103b
Improve comments, remove thresholds, simplify 2D array parameter
he29-net Jun 14, 2021
7031b3a
Add asserts to wtSample(), fix / improve OscillatorConstants.h
he29-net Jun 20, 2021
a40a81d
Remove band-limited table for SineWave shape
he29-net Jun 20, 2021
a391715
Optimize initialization of wave tables, fix normalization, fix off-by…
he29-net Jun 24, 2021
930d326
Add comment explaining the use of wave shape, waveform and wavetable …
he29-net Jun 26, 2021
322787c
Much simpler odd-number band formula thanks to Johanness
he29-net Jun 26, 2021
6225171
Fix normalization for triangle, and phase misalignment for triangle a…
he29-net Jun 27, 2021
13deb32
Fix wrong type, simplify triangle wave formula
he29-net Jun 27, 2021
c50c5be
Reduce redundant code (one universal generator lambda, common initial…
he29-net Jun 27, 2021
a93034c
Better initialization to zero
he29-net Jun 27, 2021
776d95e
Do not use band-limited wavetables if oscillator is used for modulation
he29-net Jun 27, 2021
182b205
Add a note about skipping parallel initialization with mingw compilers
he29-net Jun 28, 2021
35979b2
Allow use of band-limited wavetables by AM modulators
he29-net Jun 29, 2021
536fc1b
Merge remote-tracking branch 'upstream/master' into oscillator-fix
he29-net Jul 2, 2021
20ec10f
Enable wavetable oscillators by default for new projects and new Trip…
he29-net Jul 2, 2021
e3a0a50
Change "WT" icon to "HQ" and remove color to make it less prominent
he29-net Jul 2, 2021
4ecebd5
Add a note about m_isModulator
he29-net Jul 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/projects/templates/default.mpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<track muted="0" type="0" name="TripleOscillator" solo="0">
<instrumenttrack pan="0" fxch="0" usemasterpitch="1" pitchrange="1" pitch="0" basenote="57" vol="100">
<instrument name="tripleoscillator">
<tripleoscillator phoffset2="0" userwavefile0="" finer0="0" userwavefile1="" finer1="0" userwavefile2="" finer2="0" coarse0="0" coarse1="-12" coarse2="-24" finel0="0" finel1="0" modalgo1="2" modalgo2="2" finel2="0" pan0="0" modalgo3="2" pan1="0" stphdetun0="0" pan2="0" stphdetun1="0" wavetype0="0" stphdetun2="0" wavetype1="0" wavetype2="0" vol0="33" vol1="33" phoffset0="0" phoffset1="0" vol2="33"/>
<tripleoscillator phoffset2="0" userwavefile0="" finer0="0" userwavefile1="" finer1="0" userwavefile2="" finer2="0" coarse0="0" coarse1="-12" coarse2="-24" finel0="0" finel1="0" modalgo1="2" modalgo2="2" finel2="0" pan0="0" modalgo3="2" pan1="0" stphdetun0="0" pan2="0" stphdetun1="0" wavetype0="0" stphdetun2="0" wavetype1="0" wavetype2="0" vol0="33" vol1="33" phoffset0="0" phoffset1="0" vol2="33" useWaveTable1="1" useWaveTable2="1" useWaveTable3="1"/>
</instrument>
<eldata fres="0.5" ftype="0" fcut="14000" fwet="0">
<elvol lspd_denominator="4" sustain="0.5" pdel="0" userwavefile="" dec="0.5" lamt="0" latt="0" rel="0.1" amt="0" x100="0" att="0" lpdel="0" hold="0.5" lspd_syncmode="0" lshp="0" lspd="0.1" ctlenvamt="0" lspd_numerator="4"/>
Expand Down
1 change: 1 addition & 0 deletions include/DataFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<UpgradeMethod> UPGRADE_METHODS;
Expand Down
131 changes: 115 additions & 16 deletions include/Oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Oscillator.h - declaration of class Oscillator
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* 2018 Dave French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
Expand All @@ -25,16 +26,20 @@
#ifndef OSCILLATOR_H
#define OSCILLATOR_H

#include "lmmsconfig.h"

#include <cassert>
#include <fftw3.h>
#include <math.h>

#ifdef LMMS_HAVE_STDLIB_H
#include <stdlib.h>
#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;

Expand All @@ -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
{
Expand All @@ -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 );
Expand Down Expand Up @@ -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<f_cnt_t>(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<OscillatorConstants::waveform_t>& table, const float sample) const
{
assert(table != nullptr);
wtSampleControl control = getWtSampleControl(sample);
he29-net marked this conversation as resolved.
Show resolved Hide resolved
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<int>(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,
Expand Down
57 changes: 57 additions & 0 deletions include/OscillatorConstants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* OscillatorConstants.h - declaration of constants used in Oscillator and SampleBuffer
* for band limited wave tables
*
* Copyright (c) 2018 Dave French <dave/dot/french3/at/googlemail/dot/com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#ifndef OSCILLATORCONSTANTS_H
#define OSCILLATORCONSTANTS_H

#include <array>
he29-net marked this conversation as resolved.
Show resolved Hide resolved

#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<int>(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<sample_t, WAVETABLE_LENGTH> wavetable_t;
he29-net marked this conversation as resolved.
Show resolved Hide resolved
typedef std::array<wavetable_t, WAVE_TABLES_PER_WAVEFORM_COUNT> waveform_t;
};


#endif // OSCILLATORCONSTANTS_H
5 changes: 5 additions & 0 deletions include/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#ifndef SAMPLE_BUFFER_H
#define SAMPLE_BUFFER_H

#include <memory>
#include <QtCore/QReadWriteLock>
#include <QtCore/QObject>

Expand All @@ -36,6 +37,7 @@
#include "lmms_basics.h"
#include "lmms_math.h"
#include "shared_object.h"
#include "OscillatorConstants.h"
#include "MemoryManager.h"


Expand Down Expand Up @@ -273,6 +275,9 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject
}


std::unique_ptr<OscillatorConstants::waveform_t> m_userAntiAliasWaveTable;
JohannesLorenz marked this conversation as resolved.
Show resolved Hide resolved


public slots:
void setAudioFile(const QString & audioFile);
void loadFromBase64(const QString & data);
Expand Down
Loading