BandLimitedWave: provide wavetables as pre-generated "bin" files, these will be installed under datadir/wavetables
The runtime-generation is still there as a fallback, and the file generation code is left in as commented-out, because it might be needed in the future
This commit is contained in:
@@ -4,6 +4,7 @@ ADD_SUBDIRECTORY(presets)
|
||||
ADD_SUBDIRECTORY(projects)
|
||||
ADD_SUBDIRECTORY(samples)
|
||||
ADD_SUBDIRECTORY(themes)
|
||||
ADD_SUBDIRECTORY(wavetables)
|
||||
|
||||
IF(LMMS_BUILD_LINUX)
|
||||
INSTALL(FILES themes/default/icon.png DESTINATION "${DATA_DIR}/pixmaps" RENAME lmms.png)
|
||||
|
||||
2
data/wavetables/CMakeLists.txt
Normal file
2
data/wavetables/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
FILE(GLOB WAVETABLES *.bin)
|
||||
INSTALL(FILES ${WAVETABLES} DESTINATION "${LMMS_DATA_DIR}/wavetables")
|
||||
BIN
data/wavetables/moog.bin
Normal file
BIN
data/wavetables/moog.bin
Normal file
Binary file not shown.
BIN
data/wavetables/saw.bin
Normal file
BIN
data/wavetables/saw.bin
Normal file
Binary file not shown.
BIN
data/wavetables/sqr.bin
Normal file
BIN
data/wavetables/sqr.bin
Normal file
Binary file not shown.
BIN
data/wavetables/tri.bin
Normal file
BIN
data/wavetables/tri.bin
Normal file
Binary file not shown.
@@ -26,6 +26,11 @@
|
||||
#ifndef BANDLIMITEDWAVE_H
|
||||
#define BANDLIMITEDWAVE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
|
||||
#include "config_mgr.h"
|
||||
#include "interpolation.h"
|
||||
#include "lmms_basics.h"
|
||||
#include "lmms_math.h"
|
||||
@@ -50,26 +55,54 @@ const int TLENS[MAXTBL+1] = { 2 << 0, 3 << 0, 2 << 1, 3 << 1,
|
||||
typedef struct
|
||||
{
|
||||
public:
|
||||
inline sample_t sampleAt( int _table, int _ph )
|
||||
inline sample_t sampleAt( int table, int ph )
|
||||
{
|
||||
if( _table % 2 == 0 )
|
||||
{ return m_data[ TLENS[ _table ] + _ph ]; }
|
||||
if( table % 2 == 0 )
|
||||
{ return m_data[ TLENS[ table ] + ph ]; }
|
||||
else
|
||||
{ return m_data3[ TLENS[ _table ] + _ph ]; }
|
||||
{ return m_data3[ TLENS[ table ] + ph ]; }
|
||||
}
|
||||
inline void setSampleAt( int _table, int _ph, sample_t _sample )
|
||||
inline void setSampleAt( int table, int ph, sample_t sample )
|
||||
{
|
||||
if( _table % 2 == 0 )
|
||||
{ m_data[ TLENS[ _table ] + _ph ] = _sample; }
|
||||
if( table % 2 == 0 )
|
||||
{ m_data[ TLENS[ table ] + ph ] = sample; }
|
||||
else
|
||||
{ m_data3[ TLENS[ _table ] + _ph ] = _sample; }
|
||||
{ m_data3[ TLENS[ table ] + ph ] = sample; }
|
||||
}
|
||||
private:
|
||||
sample_t m_data [ MIPMAPSIZE ];
|
||||
sample_t m_data3 [ MIPMAPSIZE3 ];
|
||||
|
||||
} WaveMipMap;
|
||||
|
||||
|
||||
QDataStream& operator<< ( QDataStream &out, WaveMipMap &waveMipMap )
|
||||
{
|
||||
for( int tbl = 0; tbl <= MAXTBL; tbl++ )
|
||||
{
|
||||
for( int i = 0; i < TLENS[tbl]; i++ )
|
||||
{
|
||||
out << waveMipMap.sampleAt( tbl, i );
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap )
|
||||
{
|
||||
sample_t sample;
|
||||
for( int tbl = 0; tbl <= MAXTBL; tbl++ )
|
||||
{
|
||||
for( int i = 0; i < TLENS[tbl]; i++ )
|
||||
{
|
||||
in >> sample;
|
||||
waveMipMap.setSampleAt( tbl, i, sample );
|
||||
}
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
class BandLimitedWave
|
||||
{
|
||||
public:
|
||||
@@ -88,22 +121,22 @@ public:
|
||||
/*! \brief This method converts frequency to wavelength. The oscillate function takes wavelength as argument so
|
||||
* use this to convert your note frequency to wavelength before using it.
|
||||
*/
|
||||
static inline float freqToLen( float _f )
|
||||
static inline float freqToLen( float f )
|
||||
{
|
||||
return freqToLen( _f, engine::mixer()->processingSampleRate() );
|
||||
return freqToLen( f, engine::mixer()->processingSampleRate() );
|
||||
}
|
||||
|
||||
/*! \brief This method converts frequency to wavelength, but you can use any custom sample rate with it.
|
||||
*/
|
||||
static inline float freqToLen( float _f, sample_rate_t _sr )
|
||||
static inline float freqToLen( float f, sample_rate_t sr )
|
||||
{
|
||||
return static_cast<float>( _sr ) / _f;
|
||||
return static_cast<float>( sr ) / f;
|
||||
}
|
||||
|
||||
/*! \brief This method converts phase delta to wavelength. It assumes a phase scale of 0 to 1. */
|
||||
static inline float pdToLen( float _pd )
|
||||
static inline float pdToLen( float pd )
|
||||
{
|
||||
return 1.0f / _pd;
|
||||
return 1.0f / pd;
|
||||
}
|
||||
|
||||
/*! \brief This method provides interpolated samples of bandlimited waveforms.
|
||||
@@ -189,8 +222,10 @@ public:
|
||||
static void generateWaves();
|
||||
|
||||
static bool s_wavesGenerated;
|
||||
|
||||
|
||||
static WaveMipMap s_waveforms [NumBLWaveforms];
|
||||
|
||||
static QString s_wavetableDir;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
WaveMipMap BandLimitedWave::s_waveforms[4] = { };
|
||||
bool BandLimitedWave::s_wavesGenerated = false;
|
||||
QString BandLimitedWave::s_wavetableDir = "";
|
||||
|
||||
void BandLimitedWave::generateWaves()
|
||||
{
|
||||
@@ -36,117 +37,207 @@ void BandLimitedWave::generateWaves()
|
||||
|
||||
int i;
|
||||
|
||||
// set wavetable directory
|
||||
s_wavetableDir = configManager::inst()->dataDir() + "wavetables/";
|
||||
|
||||
// set wavetable files
|
||||
QFile saw_file( s_wavetableDir + "saw.bin" );
|
||||
QFile sqr_file( s_wavetableDir + "sqr.bin" );
|
||||
QFile tri_file( s_wavetableDir + "tri.bin" );
|
||||
QFile moog_file( s_wavetableDir + "moog.bin" );
|
||||
|
||||
// saw wave - BLSaw
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
// check for file and use it if exists
|
||||
if( saw_file.exists() )
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
double max = 0.0;
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
saw_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &saw_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLSaw ];
|
||||
saw_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
{
|
||||
int harm = 1;
|
||||
double s = 0.0f;
|
||||
double hlen;
|
||||
do
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
double max = 0.0;
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
hlen = static_cast<double>( len ) / static_cast<double>( harm );
|
||||
const double amp = -1.0 / static_cast<double>( harm );
|
||||
//const double a2 = cos( om * harm * F_2PI );
|
||||
s += amp * /*a2 **/sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
|
||||
harm++;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
|
||||
int harm = 1;
|
||||
double s = 0.0f;
|
||||
double hlen;
|
||||
do
|
||||
{
|
||||
hlen = static_cast<double>( len ) / static_cast<double>( harm );
|
||||
const double amp = -1.0 / static_cast<double>( harm );
|
||||
//const double a2 = cos( om * harm * F_2PI );
|
||||
s += amp * /*a2 **/sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
|
||||
harm++;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// square wave - BLSquare
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
// check for file and use it if exists
|
||||
if( sqr_file.exists() )
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
double max = 0.0;
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
sqr_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &sqr_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLSquare ];
|
||||
sqr_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
{
|
||||
int harm = 1;
|
||||
double s = 0.0f;
|
||||
double hlen;
|
||||
do
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
double max = 0.0;
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
hlen = static_cast<double>( len ) / static_cast<double>( harm );
|
||||
const double amp = 1.0 / static_cast<double>( harm );
|
||||
//const double a2 = cos( om * harm * F_2PI );
|
||||
s += amp * /*a2 **/ sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
|
||||
harm += 2;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
|
||||
int harm = 1;
|
||||
double s = 0.0f;
|
||||
double hlen;
|
||||
do
|
||||
{
|
||||
hlen = static_cast<double>( len ) / static_cast<double>( harm );
|
||||
const double amp = 1.0 / static_cast<double>( harm );
|
||||
//const double a2 = cos( om * harm * F_2PI );
|
||||
s += amp * /*a2 **/ sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
|
||||
harm += 2;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// triangle wave - BLTriangle
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
if( tri_file.exists() )
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
double max = 0.0;
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
tri_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &tri_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLTriangle ];
|
||||
tri_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
{
|
||||
int harm = 1;
|
||||
double s = 0.0f;
|
||||
double hlen;
|
||||
do
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
double max = 0.0;
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
hlen = static_cast<double>( len ) / static_cast<double>( harm );
|
||||
const double amp = 1.0 / static_cast<double>( harm * harm );
|
||||
//const double a2 = cos( om * harm * F_2PI );
|
||||
s += amp * /*a2 **/ sin( ( static_cast<double>( ph * harm ) / static_cast<double>( len ) +
|
||||
( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI );
|
||||
harm += 2;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
|
||||
int harm = 1;
|
||||
double s = 0.0f;
|
||||
double hlen;
|
||||
do
|
||||
{
|
||||
hlen = static_cast<double>( len ) / static_cast<double>( harm );
|
||||
const double amp = 1.0 / static_cast<double>( harm * harm );
|
||||
//const double a2 = cos( om * harm * F_2PI );
|
||||
s += amp * /*a2 **/ sin( ( static_cast<double>( ph * harm ) / static_cast<double>( len ) +
|
||||
( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI );
|
||||
harm += 2;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// moog saw wave - BLMoog
|
||||
// basically, just add in triangle + 270-phase saw
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
if( moog_file.exists() )
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
moog_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &moog_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLMoog ];
|
||||
moog_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
{
|
||||
const int sawph = ( ph + static_cast<int>( len * 0.75 ) ) % len;
|
||||
const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph );
|
||||
const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph );
|
||||
s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
|
||||
const int len = TLENS[i];
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
const int sawph = ( ph + static_cast<int>( len * 0.75 ) ) % len;
|
||||
const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph );
|
||||
const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph );
|
||||
s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the generated flag so we don't load/generate them again needlessly
|
||||
s_wavesGenerated = true;
|
||||
|
||||
|
||||
// generate files, serialize mipmaps as QDataStreams and save them on disk
|
||||
//
|
||||
// normally these are now provided with LMMS as pre-generated so we don't have to do this,
|
||||
// but I'm leaving the code here in case it's needed in the future
|
||||
// (maybe we add more waveforms or change the generation code or mipmap format, etc.)
|
||||
|
||||
/*
|
||||
|
||||
// if you want to generate the files, you need to set the filenames and paths here -
|
||||
// can't use the usual wavetable directory here as it can require permissions on
|
||||
// some systems...
|
||||
|
||||
QFile sawfile( "path-to-wavetables/saw.bin" );
|
||||
QFile sqrfile( "path-to-wavetables/sqr.bin" );
|
||||
QFile trifile( "path-to-wavetables/tri.bin" );
|
||||
QFile moogfile( "path-to-wavetables/moog.bin" );
|
||||
|
||||
sawfile.open( QIODevice::WriteOnly );
|
||||
QDataStream sawout( &sawfile );
|
||||
sawout << s_waveforms[ BandLimitedWave::BLSaw ];
|
||||
sawfile.close();
|
||||
|
||||
sqrfile.open( QIODevice::WriteOnly );
|
||||
QDataStream sqrout( &sqrfile );
|
||||
sqrout << s_waveforms[ BandLimitedWave::BLSquare ];
|
||||
sqrfile.close();
|
||||
|
||||
trifile.open( QIODevice::WriteOnly );
|
||||
QDataStream triout( &trifile );
|
||||
triout << s_waveforms[ BandLimitedWave::BLTriangle ];
|
||||
trifile.close();
|
||||
|
||||
moogfile.open( QIODevice::WriteOnly );
|
||||
QDataStream moogout( &moogfile );
|
||||
moogout << s_waveforms[ BandLimitedWave::BLMoog ];
|
||||
moogfile.close();
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user