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.
This commit is contained in:
Martin Pavelek
2021-07-04 13:14:59 +02:00
committed by GitHub
parent f8d7fa3b87
commit 6f8c6dba82
14 changed files with 580 additions and 75 deletions

View File

@@ -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<UpgradeMethod> UPGRADE_METHODS;

View File

@@ -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
*
@@ -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;
@@ -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<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);
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,

View File

@@ -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>
#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;
typedef std::array<wavetable_t, WAVE_TABLES_PER_WAVEFORM_COUNT> waveform_t;
};
#endif // OSCILLATORCONSTANTS_H

View File

@@ -26,6 +26,7 @@
#ifndef SAMPLE_BUFFER_H
#define SAMPLE_BUFFER_H
#include <memory>
#include <QtCore/QReadWriteLock>
#include <QtCore/QObject>
@@ -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<OscillatorConstants::waveform_t> m_userAntiAliasWaveTable;
public slots:
void setAudioFile(const QString & audioFile);
void loadFromBase64(const QString & data);